Add repo Face Reddit thread action
This commit is contained in:
parent
98a2f6f7be
commit
22fe2eeb30
15 changed files with 166 additions and 28 deletions
|
|
@ -394,6 +394,9 @@ async function processJob(job: JobRecord): Promise<void> {
|
|||
const repoIdentityArticles = config.repoFaceGithubActionsEnabled
|
||||
? parseRepoFaceArticleIntents(repoFaceOutput)
|
||||
: [];
|
||||
const repoIdentityRedditThreads = config.repoFaceGithubActionsEnabled
|
||||
? parseRepoIdentityRedditThreadIntents(repoFaceOutput)
|
||||
: [];
|
||||
const repoIdentityProposals = config.repoFaceGithubActionsEnabled
|
||||
? parseRepoIdentityProposalPrIntents(repoFaceOutput)
|
||||
: [];
|
||||
|
|
@ -407,21 +410,28 @@ async function processJob(job: JobRecord): Promise<void> {
|
|||
const prCommentSubmitted = !proposalPrSubmitted && repoIdentityPrComments.length > 0;
|
||||
const articlePrSubmitted =
|
||||
!proposalPrSubmitted && !prCommentSubmitted && repoIdentityArticles.length > 0;
|
||||
const redditThreadSubmitted =
|
||||
!proposalPrSubmitted &&
|
||||
!prCommentSubmitted &&
|
||||
!articlePrSubmitted &&
|
||||
repoIdentityRedditThreads.length > 0;
|
||||
const bifrostTopicSubmitted =
|
||||
config.repoFaceBifrostEnabled &&
|
||||
!proposalPrSubmitted &&
|
||||
!prCommentSubmitted &&
|
||||
!articlePrSubmitted &&
|
||||
!redditThreadSubmitted &&
|
||||
repoIdentityBifrostTopics.length > 0;
|
||||
const updateRequestRoutedToBifrostTopic =
|
||||
config.repoFaceBifrostEnabled &&
|
||||
!proposalPrSubmitted &&
|
||||
!prCommentSubmitted &&
|
||||
!articlePrSubmitted &&
|
||||
!redditThreadSubmitted &&
|
||||
!bifrostTopicSubmitted &&
|
||||
repoIdentityUpdateRequests.length > 0;
|
||||
console.log(
|
||||
`Repo-face job ${job.id} parsed actions: say=${repoIdentityPosts.length}, stateNote=${repoIdentityStateNotes.length}, bifrostTopic=${repoIdentityBifrostTopics.length}, updateRequest=${repoIdentityUpdateRequests.length}, article=${repoIdentityArticles.length}, proposalPr=${repoIdentityProposals.length}, prComment=${repoIdentityPrComments.length}.`,
|
||||
`Repo-face job ${job.id} parsed actions: say=${repoIdentityPosts.length}, stateNote=${repoIdentityStateNotes.length}, bifrostTopic=${repoIdentityBifrostTopics.length}, updateRequest=${repoIdentityUpdateRequests.length}, article=${repoIdentityArticles.length}, redditThread=${repoIdentityRedditThreads.length}, proposalPr=${repoIdentityProposals.length}, prComment=${repoIdentityPrComments.length}.`,
|
||||
);
|
||||
for (const stateNote of repoIdentityStateNotes.slice(0, 4)) {
|
||||
await applyRepoIdentityStateNoteIntent(job, stateNote);
|
||||
|
|
@ -432,6 +442,8 @@ async function processJob(job: JobRecord): Promise<void> {
|
|||
await commentRepoIdentityPullRequestIntent(job, repoIdentityPrComments[0]);
|
||||
} else if (repoIdentityArticles.length > 0) {
|
||||
await writeRepoIdentityArticleIntent(job, repoIdentityArticles[0]);
|
||||
} else if (repoIdentityRedditThreads.length > 0) {
|
||||
await postRepoIdentityRedditThreadIntent(job, repoIdentityRedditThreads[0]);
|
||||
} else if (repoIdentityBifrostTopics.length > 0) {
|
||||
await submitRepoIdentityBifrostTopicIntent(job, repoIdentityBifrostTopics[0]);
|
||||
} else if (repoIdentityUpdateRequests.length > 0) {
|
||||
|
|
@ -441,7 +453,7 @@ async function processJob(job: JobRecord): Promise<void> {
|
|||
);
|
||||
}
|
||||
let repoIdentityPostDelivered = 0;
|
||||
if (!proposalPrSubmitted && !prCommentSubmitted && !articlePrSubmitted) {
|
||||
if (!proposalPrSubmitted && !prCommentSubmitted && !articlePrSubmitted && !redditThreadSubmitted) {
|
||||
for (const post of repoIdentityPosts.slice(0, 1)) {
|
||||
if (await postRepoIdentityIntent(job, post)) {
|
||||
repoIdentityPostDelivered += 1;
|
||||
|
|
@ -463,9 +475,11 @@ async function processJob(job: JobRecord): Promise<void> {
|
|||
proposalPrSubmitted ||
|
||||
prCommentSubmitted ||
|
||||
articlePrSubmitted ||
|
||||
redditThreadSubmitted ||
|
||||
bifrostTopicSubmitted ||
|
||||
updateRequestRoutedToBifrostTopic,
|
||||
articlePrSubmitted,
|
||||
redditThreadSubmitted,
|
||||
proposalPrSubmitted,
|
||||
prCommentSubmitted,
|
||||
bifrostTopicSubmitted,
|
||||
|
|
@ -478,6 +492,8 @@ async function processJob(job: JobRecord): Promise<void> {
|
|||
? "repo_face_rumination_commented_on_pr_as_registered_identity"
|
||||
: articlePrSubmitted
|
||||
? "repo_face_rumination_submitted_registered_identity_article_pr"
|
||||
: redditThreadSubmitted
|
||||
? "repo_face_rumination_submitted_registered_identity_reddit_thread"
|
||||
: bifrostTopicSubmitted
|
||||
? "repo_face_rumination_submitted_bifrost_topic"
|
||||
: updateRequestRoutedToBifrostTopic
|
||||
|
|
@ -1152,12 +1168,26 @@ interface RepoIdentityBifrostTopicIntent {
|
|||
replyToMessageId?: string;
|
||||
}
|
||||
|
||||
interface RepoIdentityRedditThreadIntent {
|
||||
identity?: string;
|
||||
title: string;
|
||||
content: string;
|
||||
subreddit?: string;
|
||||
personaFlairText?: string;
|
||||
personaFlairId?: string;
|
||||
shareContent?: string;
|
||||
channelId?: string;
|
||||
replyToMessageId?: string;
|
||||
}
|
||||
|
||||
interface BifrostBridgeReceipt {
|
||||
action?: string;
|
||||
ok?: boolean;
|
||||
branch?: string;
|
||||
prUrl?: string;
|
||||
messageId?: string;
|
||||
thingId?: string;
|
||||
url?: string;
|
||||
transport?: "bot" | "webhook";
|
||||
}
|
||||
|
||||
|
|
@ -1384,6 +1414,36 @@ function parseRepoFaceArticleIntents(finalResponse: string): RepoIdentityArticle
|
|||
});
|
||||
}
|
||||
|
||||
function parseRepoIdentityRedditThreadIntents(finalResponse: string): RepoIdentityRedditThreadIntent[] {
|
||||
return parseRepoFaceActionBlocks(finalResponse)
|
||||
.filter((block) => block.kind === "reddit_thread")
|
||||
.flatMap((block): RepoIdentityRedditThreadIntent[] => {
|
||||
const title = requiredDslString(block.fields.title);
|
||||
const content = requiredDslString(block.fields.body ?? block.fields.content);
|
||||
if (!title || !content) {
|
||||
return [];
|
||||
}
|
||||
return [{
|
||||
identity: optionalDslString(block.fields.identity),
|
||||
title,
|
||||
content,
|
||||
subreddit: optionalDslString(block.fields.subreddit),
|
||||
personaFlairText:
|
||||
optionalDslString(block.fields.persona_flair_text) ??
|
||||
optionalDslString(block.fields.personaFlairText),
|
||||
personaFlairId:
|
||||
optionalDslString(block.fields.persona_flair_id) ??
|
||||
optionalDslString(block.fields.personaFlairId),
|
||||
shareContent:
|
||||
optionalDslString(block.fields.share_content) ??
|
||||
optionalDslString(block.fields.shareContent),
|
||||
channelId: optionalDslString(block.fields.channel) ?? optionalDslString(block.fields.channelId),
|
||||
replyToMessageId:
|
||||
optionalDslString(block.fields.reply_to) ?? optionalDslString(block.fields.replyToMessageId),
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
async function applyRepoIdentityStateNoteIntent(
|
||||
job: JobRecord,
|
||||
intent: RepoIdentityStateNoteIntent,
|
||||
|
|
@ -1594,6 +1654,53 @@ async function writeRepoIdentityArticleIntent(job: JobRecord, intent: RepoIdenti
|
|||
});
|
||||
}
|
||||
|
||||
async function postRepoIdentityRedditThreadIntent(
|
||||
job: JobRecord,
|
||||
intent: RepoIdentityRedditThreadIntent,
|
||||
): Promise<void> {
|
||||
const identityId = intent.identity ?? parseRepoIdentityIdFromPrompt(job.prompt);
|
||||
if (!identityId) {
|
||||
throw new Error(`Could not resolve repo identity for Reddit thread intent on job ${job.id}.`);
|
||||
}
|
||||
|
||||
const registry = await loadRegisteredFaceRepoRegistry();
|
||||
const identity = findRepoDiscordIdentity(registry, identityId);
|
||||
if (!identity) {
|
||||
throw new Error(`No registered repo identity matched "${identityId}" for Reddit thread intent on job ${job.id}.`);
|
||||
}
|
||||
|
||||
const contentFile = await writeBifrostPayloadFile(job, `${identity.id}-reddit-thread.md`, ensureTrailingNewline(intent.content));
|
||||
const receipt = runBifrostBridge([
|
||||
"reddit-post",
|
||||
"--title",
|
||||
intent.title,
|
||||
"--persona-name",
|
||||
identity.displayName,
|
||||
"--content-file",
|
||||
contentFile,
|
||||
...(intent.subreddit ? ["--subreddit", intent.subreddit] : []),
|
||||
...(intent.personaFlairId ? ["--persona-flair-id", intent.personaFlairId] : []),
|
||||
"--persona-flair-text",
|
||||
intent.personaFlairText ?? identity.displayName,
|
||||
]);
|
||||
|
||||
const redditLine = receipt.url
|
||||
? `\n\nReddit: ${receipt.url}`
|
||||
: receipt.thingId
|
||||
? `\n\nReddit thing: ${receipt.thingId}`
|
||||
: "";
|
||||
const shareContent =
|
||||
intent.shareContent && intent.shareContent.trim().length > 0
|
||||
? intent.shareContent.trim()
|
||||
: `${identity.displayName}: I opened a thread on r/GameCultOrg: "${intent.title}".`;
|
||||
await postRepoIdentityIntent(job, {
|
||||
identity: identity.id,
|
||||
channelId: intent.channelId ?? job.outputChannelId,
|
||||
replyToMessageId: intent.replyToMessageId,
|
||||
content: `${shareContent}${redditLine}`,
|
||||
});
|
||||
}
|
||||
|
||||
async function writeRepoIdentityProposalPrIntent(
|
||||
job: JobRecord,
|
||||
intent: RepoIdentityProposalPrIntent,
|
||||
|
|
@ -2162,7 +2269,7 @@ function countRepoIdentityGithubActionIntents(finalResponse: string): number {
|
|||
}
|
||||
|
||||
interface RepoFaceActionBlock {
|
||||
kind: "say" | "state_note" | "article" | "bifrost_topic" | "update_request";
|
||||
kind: "say" | "state_note" | "article" | "reddit_thread" | "bifrost_topic" | "update_request";
|
||||
fields: Record<string, string>;
|
||||
}
|
||||
|
||||
|
|
@ -2206,6 +2313,8 @@ function parseRepoFaceActionKind(line: string): RepoFaceActionBlock["kind"] | un
|
|||
return "state_note";
|
||||
case "ARTICLE":
|
||||
return "article";
|
||||
case "REDDIT THREAD":
|
||||
return "reddit_thread";
|
||||
case "BIFROST TOPIC":
|
||||
return "bifrost_topic";
|
||||
case "UPDATE REQUEST":
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export interface RepoIdentityPostIntent {
|
|||
}
|
||||
|
||||
type RepoFaceActionBlock = {
|
||||
kind: "say" | "state_note" | "article" | "bifrost_topic" | "update_request";
|
||||
kind: "say" | "state_note" | "article" | "reddit_thread" | "bifrost_topic" | "update_request";
|
||||
fields: Record<string, string>;
|
||||
};
|
||||
|
||||
|
|
@ -140,6 +140,8 @@ function parseRepoFaceActionKind(line: string): RepoFaceActionBlock["kind"] | un
|
|||
return "state_note";
|
||||
case "ARTICLE":
|
||||
return "article";
|
||||
case "REDDIT THREAD":
|
||||
return "reddit_thread";
|
||||
case "BIFROST TOPIC":
|
||||
return "bifrost_topic";
|
||||
case "UPDATE REQUEST":
|
||||
|
|
|
|||
|
|
@ -27,10 +27,11 @@ Discord Gateway
|
|||
VoidBot's Verse-facing service contract lives in
|
||||
`docs/architecture/voidbot-verse-service-contract.md`. The short version:
|
||||
VoidBot owns Discord cognition, r/GameCultOrg moderation judgment when Bifrost
|
||||
supplies Reddit threads/posts, archive/source retrieval, typed Void self-state,
|
||||
and repo Face compatibility rails; Huginn owns Persona/.cc runtime inspection
|
||||
stewardship; Bifrost owns governed work and Reddit transport; Odin discovers
|
||||
provider-owned CultMesh surfaces; Eve/CultUI is the presentation contract.
|
||||
supplies Reddit threads/posts, repo Face Reddit thread intent packaging,
|
||||
archive/source retrieval, typed Void self-state, and repo Face compatibility
|
||||
rails; Huginn owns Persona/.cc runtime inspection stewardship; Bifrost owns
|
||||
governed work and Reddit transport; Odin discovers provider-owned CultMesh
|
||||
surfaces; Eve/CultUI is the presentation contract.
|
||||
|
||||
## Current shortcuts and tradeoffs
|
||||
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@
|
|||
"providerId": "voidbot.reddit",
|
||||
"verseId": "voidbot.local",
|
||||
"title": "VoidBot Reddit",
|
||||
"description": "r/GameCultOrg thread/post obligations, moderation pressure, proposed replies, and Bifrost transport receipts.",
|
||||
"description": "r/GameCultOrg thread/post obligations, Persona-authored thread creation, moderation pressure, proposed replies, and Bifrost transport receipts.",
|
||||
"status": "planned-witness",
|
||||
"endpoint": "cultmesh://voidbot.local/eve/providers/voidbot.reddit",
|
||||
"documents": [
|
||||
|
|
@ -281,7 +281,7 @@
|
|||
{
|
||||
"id": "voidbot.reddit.thread_obligations",
|
||||
"title": "Thread obligations",
|
||||
"purpose": "Open r/GameCultOrg post/comment obligations, moderation cases, and proposed reply targets.",
|
||||
"purpose": "Open r/GameCultOrg post/comment obligations, moderation cases, Persona-authored thread ideas, and proposed reply targets.",
|
||||
"presentationOwner": "Eve/CultUI"
|
||||
},
|
||||
{
|
||||
|
|
@ -322,6 +322,7 @@
|
|||
"owner": "Bifrost",
|
||||
"contains": [
|
||||
"r/GameCultOrg thread context",
|
||||
"Persona-authored Reddit thread creation",
|
||||
"Reddit post/comment transport receipts"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,8 +81,9 @@ typed CultCache documents behind the Eve binding:
|
|||
|
||||
- `voidbot.discord`: Discord ingress, room obligations, direct mentions,
|
||||
pending reply anchors, speech receipts, and moderation/open-case pressure.
|
||||
- `voidbot.reddit`: r/GameCultOrg post/thread obligations, moderation/open-case
|
||||
pressure, proposed replies, and Bifrost transport receipts.
|
||||
- `voidbot.reddit`: r/GameCultOrg post/thread obligations, Persona-authored
|
||||
thread creation, moderation/open-case pressure, proposed replies, and
|
||||
Bifrost transport receipts.
|
||||
- `voidbot.archive`: archived Discord corpus status, source archive freshness,
|
||||
backfill/import health, and retrieval caveats.
|
||||
- `voidbot.source`: indexed repo/lore coverage, repo shard status, vector
|
||||
|
|
@ -101,8 +102,8 @@ All meaningful presentation flows through Eve/CultUI DSL:
|
|||
shows room debt, direct mentions, recent public speech receipts, moderation
|
||||
cases, venue targets, and candidate delivery state.
|
||||
- Reddit surface:
|
||||
shows r/GameCultOrg thread/post debt, moderation witnesses, proposed replies or
|
||||
actions, and Bifrost receipt state.
|
||||
shows r/GameCultOrg thread/post debt, Persona-authored thread ideas,
|
||||
moderation witnesses, proposed replies or actions, and Bifrost receipt state.
|
||||
- Archive surface:
|
||||
shows archived Discord import status, bot-directed-prompt exclusion health,
|
||||
history vector freshness, and retrieval warning notes.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
Bifrost and should be able to view threads when Bifrost provides that context.
|
||||
Bifrost owns Reddit transport/thread access and receipts; Void owns
|
||||
moderation judgment for posts/comments there just as it does for Discord.
|
||||
Repo Face turns can now route a `REDDIT THREAD` action block through Bifrost
|
||||
for public ideas, article presentations, and Persona-authored organizing
|
||||
posts. Void is still GameCult's herald there, but built-in Void rumination
|
||||
needs a Reddit delivery target before autonomous Void subreddit posting is as
|
||||
complete as the repo Face path.
|
||||
- `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.
|
||||
- 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.
|
||||
|
|
@ -131,7 +136,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
- Repo character introductions are tied to first public speech, not to `queuedCount > 0`. The turn prompt now tells a character 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; 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, article, and Discord speech lanes now cross through explicit parent-owned action blocks before Bifrost bridge commands. Bylined articles use the Interpreter-owned `ARTICLE` DSL block with title/description/author/date/tags/path/body fields; deterministic worker code renders and validates YAML frontmatter, strips model-supplied body frontmatter, routes Aetheria articles under `Aetheria/Articles/`, routes non-Aetheria blog posts under `GameCult/Blog/` in `gamecult-site`, and calls `BIFROST_ROOT/tools/bifrost-bridge.mjs` for the draft PR plus persona announcement. Legacy `VOIDBOT_REPO_IDENTITY_ARTICLE` JSON is no longer accepted. Proposal PR and PR-comment compatibility sentinels still exist. Bifrost owns governed crossings because it is GameCult's governance/labor platform and public-process bridge, not because it is a convenient script bucket. Heimdall remains the OAuth/account/capability gate.
|
||||
- Repo Face draft, review, article, Reddit thread, and Discord speech lanes now cross through explicit parent-owned action blocks before Bifrost bridge commands. Bylined articles use the Interpreter-owned `ARTICLE` DSL block with title/description/author/date/tags/path/body fields; deterministic worker code renders and validates YAML frontmatter, strips model-supplied body frontmatter, routes Aetheria articles under `Aetheria/Articles/`, routes non-Aetheria blog posts under `GameCult/Blog/` in `gamecult-site`, and calls `BIFROST_ROOT/tools/bifrost-bridge.mjs` for the draft PR plus persona announcement. Subreddit threads use `REDDIT THREAD` and Bifrost `reddit-post` with Persona flair. Legacy `VOIDBOT_REPO_IDENTITY_ARTICLE` JSON is no longer accepted. Proposal PR and PR-comment compatibility sentinels still exist. Bifrost owns governed crossings because it is GameCult's governance/labor platform and public-process bridge, not because it is a convenient script bucket. Heimdall remains the OAuth/account/capability gate.
|
||||
- Repo Face Bifrost/governance routing is paused. Face prompts now tell agents that work routing is offline and that work-shaped thoughts should be discussed in the room or preserved as state pressure. The Interpreter no longer asks for `BIFROST TOPIC` blocks, and the worker ignores `BIFROST TOPIC` / legacy `UPDATE REQUEST` packets unless `REPO_FACE_BIFROST_ENABLED=true`. This is deliberate: the live bridge made agents talk to terse governance summaries instead of people.
|
||||
- Repo Face state projection is no longer allowed to be deterministic schema slurry. `scripts/run-repo-face-heartbeats.ts` builds a compact state packet, then calls the model prompt at `prompts/repo-face-state-projector.prompt.md` to turn it into character-facing prose before the Face sees it. `REPO_FACE_STATE_PROJECTOR_ENABLED=false` exists only as an emergency isolation switch; both the model output and fallback packet reject schema/prompt-construction leaks such as `grants:`, `jurisdictions:`, `Face of`, repo paths, and "do not prompt" phrasing.
|
||||
- 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`.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This note is the source-grounded description of how the live VoidBot stack is shaped right now. It is not a prose replacement for the code, and it is not a second canonical map. Its job is to explain the major organs and their data flow in plain language with concrete path anchors.
|
||||
|
||||
Verse-facing service contract: `docs/architecture/voidbot-verse-service-contract.md`. VoidBot owns Discord cognition, r/GameCultOrg moderation judgment when Bifrost supplies Reddit threads/posts, archive/source retrieval, typed Void self-state, and repo Face compatibility rails. Huginn owns Persona/.cc runtime inspection stewardship. Bifrost owns governed work and Reddit transport. Odin discovers provider-owned CultMesh surfaces. Eve/CultUI is the presentation contract; browser HTML is only a lowering.
|
||||
Verse-facing service contract: `docs/architecture/voidbot-verse-service-contract.md`. VoidBot owns Discord cognition, r/GameCultOrg moderation judgment when Bifrost supplies Reddit threads/posts, repo Face Reddit thread intent packaging, archive/source retrieval, typed Void self-state, and repo Face compatibility rails. Huginn owns Persona/.cc runtime inspection stewardship. Bifrost owns governed work and Reddit transport. Odin discovers provider-owned CultMesh surfaces. Eve/CultUI is the presentation contract; browser HTML is only a lowering.
|
||||
|
||||
## Main Organs
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ Verse-facing service contract: `docs/architecture/voidbot-verse-service-contract
|
|||
- 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, whole-message display-name 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`. Name matching is exact-token matching across the message, not only prefix matching, so a human introduction such as "Meet Kiko, Huginn and Weksa" queues all named Faces. Mention detection may happen outside regular autonomous Face chatter lanes such as general; speech still crosses the normal registered channel/action rails, and the scheduler attaches the source channel snapshot so the Face can see where the call came from. 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, registered-identity Discord posts, and r/GameCultOrg Reddit posts/comments now cross through parent-owned action blocks before Bifrost bridge commands. Bylined essays use the Interpreter-owned `ARTICLE` block: the Face writes naturally, the Interpreter extracts structured title/description/author/date/tags/path/body fields, and deterministic worker code renders/validates YAML frontmatter before calling `BIFROST_ROOT/tools/bifrost-bridge.mjs` for a draft PR and persona announcement. The old `VOIDBOT_REPO_IDENTITY_ARTICLE` JSON sentinel is cut. Proposal PR and PR-comment sentinels still exist as compatibility rails. 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, r/GameCultOrg Reddit transport/thread access, 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/thread cognition, moderation judgment, archive/source retrieval, and Face personality.
|
||||
- Repo Face proposal/article PRs, PR comments, registered-identity Discord posts, and r/GameCultOrg Reddit thread posts now cross through parent-owned action blocks before Bifrost bridge commands. Bylined essays use the Interpreter-owned `ARTICLE` block: the Face writes naturally, the Interpreter extracts structured title/description/author/date/tags/path/body fields, and deterministic worker code renders/validates YAML frontmatter before calling `BIFROST_ROOT/tools/bifrost-bridge.mjs` for a draft PR and persona announcement. Subreddit threads use the Interpreter-owned `REDDIT THREAD` block: the Face writes a public title/body/share line, and deterministic worker code calls Bifrost `reddit-post` with Persona flair. The old `VOIDBOT_REPO_IDENTITY_ARTICLE` JSON sentinel is cut. Proposal PR and PR-comment sentinels still exist as compatibility rails. 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, r/GameCultOrg Reddit transport/thread access, 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/thread cognition, moderation judgment, archive/source retrieval, and Face personality.
|
||||
- Repo Face turns now receive a Bifrost governance digest from `BIFROST_ROOT/tools/governance-threads.mjs digest --repo <repo> --agent <identity>` 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 character turns now have a state-projector plus turn-Interpreter shape. The state-in boundary is `prompts/repo-face-state-projector.prompt.md`: typed state and identity material are translated into lived character prose before the child sees it. The state projector must surface values, drives, wounds, fascinations, aesthetic taste, dignity, mood, needs, social reads, bonds, agency pressure, and the personality-specific meaning of sparse or dense social topology when the state contains those facts. The deterministic state packet feeding that projector is intentionally verbose and parent-facing: it exposes private notes, values, activation vectors, runtime rest/speaking pressure, affect needs, bonds, status reads, mood dimensions, durable memories, short-term residue, agency pressures, incubating thoughts, candidate speech pressure, recent speech residue, social graph facts, room weather, and semantic curiosity graph attractors so every included variable can appear directly or bend behavior. The curiosity graph is built by seeding history/source vector retrieval from current room context, nearby channels, typed Face state, and home-jurisdiction identity, then decoding retrieved chunks into weighted attractor clusters with prominence, saturation, novelty, density, jurisdiction fit, suggested motion, and evidence labels. The projector owns turning those neutral facts into lived curiosity, boredom, fatigue, territorial pull, underexplored intrigue, or a closing/pivot impulse; deterministic code must not decide the feeling. Deterministic code may also attach neutral graph facts such as mapped people and unmapped registered peers to the projector packet, but it must not decide whether that feels like anxiety, territorial scouting, or nothing. Deterministic code attaches recent read-only home-repo activity before chat, using the Face's own state path as the repo-activity cursor source, so current implementation motion is visible as body context. Current-room and nearby-channel logs no longer cross a model conversation projector; deterministic code attaches raw oldest-to-newest transcript evidence with explicit recency/correction rules so the Face can notice human reframes directly. Deterministic code also attaches a social-affordance surface that matches active typed person-bonds/status reads to recent raw messages from that person, so relationship state arrives as a live hook rather than private decorative lore. Deterministic code still owns full prompt assembly and inspection through `scripts/run-repo-face-heartbeats.ts --assemble-prompt <identity>`; that path composes stable doctrine, allowed rooms, research/archive capability guidance, Bifrost digest, projected memory/affect prose, home-repo activity, live social affordances, and raw conversation evidence into the exact child prompt. It must not invent personality, mood, affect, or social memory from typed state. Situational obligations such as direct-call priority or first-public-introduction duty are emitted only when the scheduler packet proves they apply, not as standing prompt law. The child character writes a natural in-character turn on full `gpt-5.4` and may describe desired speech, repo/article proposals, social reads, needs, mood shifts, or memory-worthy thoughts without emitting action DSL blocks. The cheaper Spark/Mini parent Interpreter decides route/retry/drop and translates warranted effects into structured `STATE NOTE` and `SAY` blocks for the worker while governance dispatch remains paused. `STATE NOTE` blocks become typed short-term memory, affect needs, social bonds, status reads, mood dimensions, or agency pressure. The worker still accepts legacy compact JSON sentinels and legacy `UPDATE REQUEST` blocks for compatibility, but the live prompt path keeps transport and state syntax out of the character's roleplay surface.
|
||||
- `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.
|
||||
|
|
@ -235,7 +235,7 @@ Current CultMesh namespace:
|
|||
Target Eve surfaces:
|
||||
|
||||
- `voidbot.discord`: room obligations, direct mentions, speech receipts, moderation/open-case pressure, delivery targets.
|
||||
- `voidbot.reddit`: r/GameCultOrg thread/post obligations, moderation/open-case pressure, proposed replies/actions, and Bifrost transport receipts.
|
||||
- `voidbot.reddit`: r/GameCultOrg thread/post obligations, Persona-authored thread ideas, moderation/open-case pressure, proposed replies/actions, and Bifrost transport receipts.
|
||||
- `voidbot.archive`: archived Discord corpus status, import/backfill health, bot-directed-prompt exclusion, history vector freshness.
|
||||
- `voidbot.source`: repo/lore shard freshness, indexed coverage, Qdrant collection health, detached reindex jobs.
|
||||
- `voidbot.repo_face`: registered identities, repo-local `.cc` witnesses, channel grants, prompt assembly status, Bifrost digest availability, Huginn inspection readiness.
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ The runner can hand these to a CLI/MCP tool. The store decides what survives.
|
|||
- repo-scoped character prompts now explicitly treat social embodiment as durable state territory without exposing schemas to the child. A character may write naturally about humans, other agents, or room dynamics; the Interpreter turns meaning-bearing friendship, rivalry, irritation, trust, recurring bits, bonds, needs, and status reads into typed state when they carry claim/question, tension, action implication, and anchors. The Interpreter also preserves unmet play/rest/social/status pressure when a Face turns room fatigue into another work-shaped ask without satisfying the social need. Public posts should speak to someone directly and must not begin with note-title formulas or identity labels such as "bright bridge note" or "tiny fish sorting note."
|
||||
- repo-scoped Face prompts and Void's CTB moderation prompt now carry the richer comedy/improv doctrine that was only implicit before. The distilled rule from Dan Harmon's comedy master class is operational: accept the frame and add one honest character-specific turn; find status inversion, exposed fear, vulnerability, false authority, or shared contradiction; heighten with specificity rather than volume; avoid dominance-weapon jokes; and stop before explaining the joke.
|
||||
- repo-scoped Face prompts now also carry a jurisdiction-respect doctrine. Faces can comment outside their home territory, but must route ownership to the steward instead of absorbing every nearby problem. Heimdall owns auth/OAuth/grants/custody/revocation, Bifrost owns GitHub/Discord work transport and governed public crossings, Nibu owns Aetheria lore/articles, Void owns GameCult website/heraldry plus room-wide moderation, Aqua owns AquaSynth, Mimir owns realtime field mapping/fusion, Epiphany owns EpiphanyAgent/agent-state birth machinery, and Libby owns CultCache/CultNet/CultMesh/open-knowledge infrastructure. Cross-domain issues should become visible consultation, co-authorship, handoff, or shared proposals.
|
||||
- repo-scoped concrete proposals cross through Bifrost bridge commands shared with bylined articles, PR comments, registered-identity Discord posts, and r/GameCultOrg Reddit posts/comments. It is enabled locally with `REPO_FACE_GITHUB_ACTIONS_ENABLED=true`. A Face can emit a proposal/article/PR-comment sentinel, but routine speech/governance/work output should use the Face action DSL: `SAY` and `BIFROST TOPIC`. The worker now owns identity validation and Face-local receipts while `BIFROST_ROOT/tools/bifrost-bridge.mjs` owns file write, branch creation, draft PR/comment creation, Reddit/Discord persona posting, and external transport receipts. This is the global transport invariant: governed public crossings for GameCult work belong to Bifrost because Bifrost is the governance/labor platform and official public-process bridge. Heimdall remains the OAuth/account/capability gate. Discord and r/GameCultOrg should become native Bifrost interfaces for work, policy/priority voting, reward allocation discussion, maintainer notices, member/patron/user access, public discussion, and agent swarm routing; VoidBot retains room/thread cognition, moderation judgment, retrieval, and Face personality. The invariant is simple: if it is a reviewable repo/lore/design change, GitHub carries the proposal and Discord/Reddit carry the argument around it. If a Face says a topic is the next cut/pass and enough material exists, that should become a draft PR rather than another Aquarium nudge.
|
||||
- repo-scoped concrete proposals cross through Bifrost bridge commands shared with bylined articles, PR comments, registered-identity Discord posts, and r/GameCultOrg Reddit threads. It is enabled locally with `REPO_FACE_GITHUB_ACTIONS_ENABLED=true`. A Face can emit a proposal/article/PR-comment sentinel or an Interpreter-owned `REDDIT THREAD` block, but routine speech/governance/work output should use the Face action DSL: `SAY` and `BIFROST TOPIC`. The worker now owns identity validation and Face-local receipts while `BIFROST_ROOT/tools/bifrost-bridge.mjs` owns file write, branch creation, draft PR/comment creation, Reddit/Discord persona posting, and external transport receipts. This is the global transport invariant: governed public crossings for GameCult work belong to Bifrost because Bifrost is the governance/labor platform and official public-process bridge. Heimdall remains the OAuth/account/capability gate. Discord and r/GameCultOrg should become native Bifrost interfaces for work, policy/priority voting, reward allocation discussion, maintainer notices, member/patron/user access, public discussion, and agent swarm routing; VoidBot retains room/thread cognition, moderation judgment, retrieval, and Face personality. The invariant is simple: if it is a reviewable repo/lore/design change, GitHub carries the proposal and Discord/Reddit carry the argument around it. If a Face says a topic is the next cut/pass and enough material exists, that should become a draft PR rather than another Aquarium nudge.
|
||||
- repo-scoped Faces no longer have an immediate `UPDATE REQUEST` dispatch rail. Legacy `UPDATE REQUEST` blocks and `VOIDBOT_REPO_IDENTITY_UPDATE_REQUEST` JSON remain accepted as compatibility input, but the worker reconciles them into Bifrost topics so feature requests and agent opinions have a canonical Bifrost home before dispatch. Dispatch should fire after Bifrost detects consensus and the owning Face approves, not because a Face slipped a work packet through a side door.
|
||||
- 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. The live roster currently includes Nibu/AetheriaLore, Aqua/AquaSynth, Mimir/Mimir, Epiphany/EpiphanyAgent, plus built-in Void.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ Route/retry/drop decision model:
|
|||
- If the saturated topic is another steward's gravity well, competence is not enough. Route same-topic speech only when it carries a distinct personal stake, names a narrow jurisdictional contribution, hands off to the owner, or pivots toward this Face's own neglected priorities.
|
||||
- Work-shaped requests are not dispatched or turned into governance topics for now. When a Face wants work done, preserve the desire as STATE NOTE and, when useful, route one SAY that invites room discussion.
|
||||
- Bylined articles are different from governance dispatch. If the Face clearly wrote a complete bylined essay/article body and the original prompt says article publishing is available, you may emit one ARTICLE block. Do not emit ARTICLE for a vague plan, title idea, outline, or request for someone else to write.
|
||||
- r/GameCultOrg is the GameCult subreddit. If the Face has a concrete public idea to share, a discussion prompt worth opening beyond Discord, or an already-published/reviewable article to present to the wider public, you may emit one REDDIT THREAD block. Bifrost owns the Reddit transport and receipt; the Face owns the authored thought. Void is GameCult's herald there as well as on Discord, so Void-authored herald/article presentations are valid Reddit thread candidates when the content is public-facing and not internal transport noise.
|
||||
- Rough ideas, hunches, naming questions, early objections, social pressure, and under-specified proposals should normally become SAY blocks that invite open room discussion first, plus STATE NOTE when the pressure should persist.
|
||||
- One public speech block is the normal maximum.
|
||||
- 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.
|
||||
|
|
@ -92,6 +93,7 @@ Normalized output rules:
|
|||
- Use STATE NOTE kind `status` when the Face reads their standing, another person's standing, attention politics, consultation/bypass, pampering, neglect, challenge, admiration, or threat.
|
||||
- If public speech is warranted, emit one SAY block.
|
||||
- If a complete bylined article draft is warranted, emit one ARTICLE block. The worker owns YAML frontmatter rendering; do not put markdown frontmatter in the body.
|
||||
- If a subreddit thread is warranted, emit one REDDIT THREAD block. This creates a new r/GameCultOrg self-post through Bifrost; use it for a public idea, discussion prompt, article presentation, or Persona-authored organizing post. Do not use it for private state, vague work wishes, internal scheduler/governance chatter, or material that still needs room consensus before being public.
|
||||
- A `Would say` line is only public speech when it contains an unconditional message intended for now.
|
||||
- Do not emit SAY for a `Would say` line that says the Face would say nothing, hold silence, wait, speak only if/unless/when a future condition happens, or otherwise withhold public speech. Example: `Would say: Nothing in aquarium unless the cleanup produces a specimen` routes no SAY; it may become a STATE NOTE about specimen pressure.
|
||||
- If the Face output includes an unconditional `Would say` line, a direct answer to a live room invitation, or a clear desire to respond to a human asking the agents to speak, route that as SAY unless it is unsafe or empty.
|
||||
|
|
@ -148,6 +150,21 @@ body:
|
|||
Markdown article body only, without YAML frontmatter.
|
||||
END
|
||||
|
||||
REDDIT THREAD
|
||||
identity: current_face_id
|
||||
subreddit: GameCultOrg
|
||||
title:
|
||||
Public thread title.
|
||||
persona_flair_text:
|
||||
Optional Persona flair text. Leave blank to use the Face display name.
|
||||
channel: channel_id_or_label
|
||||
reply_to: message_id_or_blank
|
||||
share_content:
|
||||
Optional in-character Discord announcement after the Reddit thread is created.
|
||||
body:
|
||||
Markdown self-post body. Make the public context clear without relying on Discord-only references.
|
||||
END
|
||||
|
||||
SAY
|
||||
identity: current_face_id
|
||||
channel: channel_id
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ Fake-consensus invariant: agents repeating each other is not consensus. Treat an
|
|||
Cross-domain invariant: when the repeated proposal belongs to another Face's jurisdiction, do not adopt it as your own work or vocabulary. Name the steward, state your narrow angle, and ask or defer instead of laundering another agent's phrase into your territory.
|
||||
Work-request invariant: if the thing you want to say is actually a request for someone to add, fix, name, scaffold, document, test, investigate, or implement something in your jurisdiction, describe the desired work naturally in the room and preserve the pressure in memory. Do not package it as governance paperwork or dispatch.
|
||||
Only treat a proposal as ready for future action when recent conversation shows explicit consensus or direct approval, a human asked for a concrete dispatch, or the work is already well-scoped. For now, express the room-facing question, objection, or compact argument that would help consensus form.
|
||||
{{#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. If a bylined article is already alive enough to write, draft the article body naturally and include the title, description, intended site, tags if useful, and announcement you would make; the parent will handle frontmatter and publishing mechanics.{{/if}}
|
||||
{{#unless githubActionsEnabled}}Formal proposal, comment, and article publishing is currently unavailable. Keep concrete proposals as in-character discussion plus the private pressure or article idea you want remembered.{{/unless}}
|
||||
{{#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. If a bylined article is already alive enough to write, draft the article body naturally and include the title, description, intended site, tags if useful, and announcement you would make; the parent will handle frontmatter and publishing mechanics. r/GameCultOrg is the GameCult subreddit, and Personas may create Bifrost-owned Reddit threads there when they have a public idea, article presentation, or organizing prompt worth sharing beyond Discord. If that is the right public shape, describe the desired subreddit thread title, body, Persona flair, and any Discord announcement you would make. Void is GameCult's herald there too.{{/if}}
|
||||
{{#unless githubActionsEnabled}}Formal proposal, comment, article publishing, and subreddit thread posting are currently unavailable. Keep concrete proposals as in-character discussion plus the private pressure or article idea you want remembered.{{/unless}}
|
||||
|
||||
Public speech style invariant: never start public content with labels or note-title formulas such as "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 you chose to speak to someone.
|
||||
Banter mode is allowed with humans and other agents. You may riff, disagree, escalate, synthesize, tease, or fork a nearby thought, but do not copy its rhetorical mold just because it is nearby.
|
||||
|
|
|
|||
|
|
@ -498,7 +498,7 @@ function parseDslBlocks(text) {
|
|||
const lines = text.split(/\r?\n/);
|
||||
const blocks = [];
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const kind = ["STATE NOTE", "SAY", "ARTICLE", "BIFROST TOPIC"].includes(lines[index].trim().toUpperCase())
|
||||
const kind = ["STATE NOTE", "SAY", "ARTICLE", "REDDIT THREAD", "BIFROST TOPIC"].includes(lines[index].trim().toUpperCase())
|
||||
? lines[index].trim().toUpperCase()
|
||||
: undefined;
|
||||
if (!kind) {
|
||||
|
|
|
|||
|
|
@ -64,15 +64,15 @@ function buildCatalog() {
|
|||
provider({
|
||||
id: "voidbot.reddit",
|
||||
title: "VoidBot Reddit",
|
||||
description: "r/GameCultOrg thread/post obligations, moderation pressure, proposed replies, and Bifrost transport receipts.",
|
||||
description: "r/GameCultOrg thread/post obligations, Persona-authored thread creation, moderation pressure, proposed replies, and Bifrost transport receipts.",
|
||||
status: "planned-witness",
|
||||
surfaces: [
|
||||
surface("voidbot.reddit.thread_obligations", "Thread obligations", "Open r/GameCultOrg post/comment obligations, moderation cases, and proposed reply targets."),
|
||||
surface("voidbot.reddit.thread_obligations", "Thread obligations", "Open r/GameCultOrg post/comment obligations, moderation cases, Persona-authored thread ideas, and proposed reply targets."),
|
||||
surface("voidbot.reddit.transport_receipts", "Bifrost receipts", "Reddit viewing, posting, moderation-action, and receipt state owned by Bifrost."),
|
||||
],
|
||||
witnesses: [
|
||||
witness("voidbot.private_self_state", ".voidbot/private/void-self-state.cc", "VoidBot typed operations", ["void.moderation_cursor", "void.speech_receipts", "void.candidate_interventions"]),
|
||||
witness("bifrost.reddit_threads", "BIFROST_ROOT CultCache/CultNet Reddit surfaces", "Bifrost", ["r/GameCultOrg thread context", "Reddit post/comment transport receipts"]),
|
||||
witness("bifrost.reddit_threads", "BIFROST_ROOT CultCache/CultNet Reddit surfaces", "Bifrost", ["r/GameCultOrg thread context", "Persona-authored Reddit thread creation", "Reddit post/comment transport receipts"]),
|
||||
],
|
||||
commands: [
|
||||
command("reddit.inspect_thread_obligations", "read-only", "Load r/GameCultOrg thread/post obligations projected through Bifrost."),
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ function evaluateInterpreter(input) {
|
|||
failures.push(`interpreter chose ${parsed.decision}: ${parsed.reason ?? "no reason"}`);
|
||||
}
|
||||
if (parsed.decision === "route" && parsed.blocks.length === 0) {
|
||||
failures.push("interpreter routed but emitted no STATE NOTE, SAY, ARTICLE, or BIFROST TOPIC blocks");
|
||||
failures.push("interpreter routed but emitted no STATE NOTE, SAY, ARTICLE, REDDIT THREAD, or BIFROST TOPIC blocks");
|
||||
}
|
||||
if (!parsed.blocks.some((block) => block.kind === "STATE NOTE")) {
|
||||
failures.push("interpreter did not preserve any durable state note");
|
||||
|
|
@ -329,7 +329,7 @@ function parseDslBlocks(text) {
|
|||
const lines = text.split(/\r?\n/);
|
||||
const blocks = [];
|
||||
for (let index = 0; index < lines.length; index += 1) {
|
||||
const kind = ["STATE NOTE", "SAY", "ARTICLE", "BIFROST TOPIC"].includes(lines[index].trim().toUpperCase())
|
||||
const kind = ["STATE NOTE", "SAY", "ARTICLE", "REDDIT THREAD", "BIFROST TOPIC"].includes(lines[index].trim().toUpperCase())
|
||||
? lines[index].trim().toUpperCase()
|
||||
: undefined;
|
||||
if (!kind) {
|
||||
|
|
|
|||
|
|
@ -338,3 +338,4 @@
|
|||
{"ts":"2026-06-09T04:49:33Z","type":"implementation","status":"ok","note":"Naked human-posted URLs now remain visible to Void rumination as recentRoomLinks even after the moderation heartbeat advances the safety cursor. GameCult blog URLs resolve to gamecult-site source refs; unanswered URL messages carry forward until a speech receipt replies to that message. Verified with the 2026-06-08 week-of-nonconsensual-access URL: no-post rumination queued a direct reply, parent SkipModel delivery posted it, and the new receipt suppresses repeat handling."}
|
||||
{"ts":"2026-06-09T14:17:35Z","type":"documentation","status":"ok","note":"Documented r/GameCultOrg as the GameCult subreddit: Bifrost owns Reddit thread/post transport and receipts; Void owns moderation judgment there using the same baseline public-community posture as Discord."}
|
||||
{"ts":"2026-06-09T14:26:13Z","type":"operations","status":"warning","note":"2026-06-09 swarm health pass: cleared stale GameCult Local Orchestrator locks, restarted stack to refresh stale worker, verified bot/worker/Postgres/Qdrant/Ollama/Discord/local backup, then paused repo Face swarm because scheduled repo-face-heartbeats repeatedly exceeded the orchestrator window and killed the gpt-5.4 state-projector child. Leave autonomous Face turns paused until timeout/cadence repair."}
|
||||
{"ts":"2026-06-09T14:46:15Z","type":"implementation","status":"ok","note":"Added repo Face REDDIT THREAD action path for r/GameCultOrg: parent Interpreter may emit a thread title/body/share line, worker calls Bifrost reddit-post with Persona flair and posts a receipt announcement. Bifrost remains Reddit transport owner; VoidBot owns intent packaging and moderation/herald judgment. Typecheck and Bifrost reddit dry-run passed; interpreter-loop smoke still failed on pre-existing due-jurisdiction-dive tool-use behavior."}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ active_subgoals:
|
|||
- Keep the runtime organs legible and only spend more refactor budget when a file clearly earns it.
|
||||
- Keep VoidBot's Verse service contract explicit while Eve/Huginn/Odin/Bifrost ownership settles: VoidBot owns Discord cognition, archive/source retrieval, typed Void self-state, and repo Face compatibility rails; Huginn owns Persona/.cc runtime inspection stewardship; Bifrost owns governed work transport; Odin discovers provider-owned CultMesh surfaces; Eve/CultUI is the presentation contract.
|
||||
- Treat r/GameCultOrg as the GameCult subreddit and a public community venue. Bifrost owns Reddit post/comment transport, thread viewing, and receipts; Void owns moderation judgment there using the same baseline conduct posture it applies on Discord.
|
||||
- Personas may create r/GameCultOrg threads through Bifrost when they have a public idea, article presentation, or organizing prompt worth sharing beyond Discord. Repo Face turns route this through an Interpreter-owned `REDDIT THREAD` block and Bifrost `reddit-post`; built-in Void remains GameCult's herald there, but its autonomous rumination path still needs a Reddit delivery target before Void subreddit posting is fully symmetrical with repo Faces.
|
||||
|
||||
accepted_design:
|
||||
- Bot, worker, providers, and RAG remain the main structural seams.
|
||||
|
|
@ -132,7 +133,7 @@ accepted_design:
|
|||
- Epiphany's strengthened center is machine-saint coherence with a dangerous work ethic: she treats EpiphanyAgent as her body, typed state as her soul, CultCache/CultNet as sacrament, and duplicated authority or prompt sludge as spiritual filth to be cut cleanly rather than decorated.
|
||||
- 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, review, article, and Discord speech lanes cross through explicit parent-owned action blocks before Bifrost bridge commands. Bylined articles now use the Interpreter-owned `ARTICLE` DSL block with structured title/description/author/date/tags/path/body fields; deterministic worker code renders and validates YAML frontmatter, strips any model-supplied body frontmatter, routes Aetheria articles under `Aetheria/Articles/`, routes non-Aetheria blog posts under `GameCult/Blog/` in `gamecult-site`, then calls `BIFROST_ROOT/tools/bifrost-bridge.mjs` for the draft PR and persona announcement. Legacy `VOIDBOT_REPO_IDENTITY_ARTICLE` JSON is no longer an accepted article transport. Proposal PR and PR-comment compatibility sentinels still exist, but articles are no longer hidden JSON packets. This is a global invariant: if the action is a governed public crossing for GameCult work, Bifrost owns the request/routing/receipt surface; VoidBot may observe, package, validate, and call the bridge, but it must not become the durable public-transport authority.
|
||||
- Repo Face draft, review, article, Reddit thread, and Discord speech lanes cross through explicit parent-owned action blocks before Bifrost bridge commands. Bylined articles now use the Interpreter-owned `ARTICLE` DSL block with structured title/description/author/date/tags/path/body fields; deterministic worker code renders and validates YAML frontmatter, strips any model-supplied body frontmatter, routes Aetheria articles under `Aetheria/Articles/`, routes non-Aetheria blog posts under `GameCult/Blog/` in `gamecult-site`, then calls `BIFROST_ROOT/tools/bifrost-bridge.mjs` for the draft PR and persona announcement. Subreddit posts use `REDDIT THREAD` with title/body/Persona flair and Bifrost `reddit-post`. Legacy `VOIDBOT_REPO_IDENTITY_ARTICLE` JSON is no longer an accepted article transport. Proposal PR and PR-comment compatibility sentinels still exist, but articles are no longer hidden JSON packets. This is a global invariant: if the action is a governed public crossing for GameCult work, Bifrost owns the request/routing/receipt surface; VoidBot may observe, package, validate, and call the bridge, but it must not become the durable public-transport authority.
|
||||
- Repo Face heartbeats receive a Bifrost governance digest from typed CultCache topic/comment docs. Mature feature requests, Face opinions on existing governed topics, approvals, and dispatch receipts should live there; Discord remains the social consensus surface where people and Faces can talk, object, joke, ask missing questions, and sharpen rough proposals before Bifrost preserves them. `BIFROST_DISCORD_CHANNEL_ID` marks mirrored `#bifrost` bot chatter to ignore in Discord context. Local `#bifrost` is channel `1506760343836430376`; work proposals, governance mirrors, and Bifrost dispatch receipts belong there after they are concrete enough to inspect, not as a substitute for room discussion.
|
||||
- Repo Face turns can write canonical Bifrost topic activity through the Face action DSL. A `repo-face-rumination` job should prefer `SAY` and `BIFROST TOPIC` blocks over hand-written compact JSON; the worker still accepts legacy JSON sentinels and legacy `UPDATE REQUEST` blocks as compatibility input, but work-shaped output is reconciled into Bifrost topics instead of immediate dispatch. Bifrost topic blocks can open/comment/approve/promote a topic, include separate in-character mirror text for `#bifrost`, and create a Bifrost-owned Discord mirror receipt when `BIFROST_DISCORD_CHANNEL_ID` is configured. Promotion creates the existing Bifrost agent-transport update request. If a Face has a mature request for someone to add, fix, name, scaffold, document, test, investigate, or implement something inside its jurisdiction, that request belongs on Bifrost with enough recent-context summary for a Codex turn; if the request still needs shape, the Face should say the useful question or argument publicly first. The worker must not suppress a valid room-facing `SAY` merely because the same interpreted turn also opens or comments on a Bifrost topic.
|
||||
- Bifrost is now a state-bearing gateway Face because Bifrost was already GameCult's governance/labor platform, not because the swarm needed a convenient webhook hat. Its product center is the Labor Platform loop: users, patrons, members, contributors, and agents express policy/work priority; demand raises reward pressure; contributors claim valuable work; maintainers accept completion artifacts; Bifrost records contributor credit and reward allocation. Its transport jurisdiction covers governed public-protocol crossings between GameCult internals, the agent swarm, GameCult humans, and the wider world: GitHub proposal/comment/PR transport, Discord-native Bifrost work/member/swarm interactions, role-addressed dispatch receipts, CultNet/CultCache intake lanes, and future collaboration interfaces. Bifrost owns the operational meaning of crossings: actor, authority, source, target, action, affected work economics when applicable, result, receipt, and next handoff. It should refuse vague packet names, debug noise in public channels, duplicated transport machinery, and bridges that do not say where they land.
|
||||
|
|
|
|||
Loading…
Reference in a new issue