Require parent interpreter for persona speech
This commit is contained in:
parent
ad1e288309
commit
ccaf856b53
6 changed files with 32 additions and 69 deletions
|
|
@ -574,14 +574,6 @@ async function executeRepoPersonaJobWithInterpreter(
|
|||
});
|
||||
}
|
||||
|
||||
const firstObligationRoute = routeUnparsedWouldSay(firstResponse, firstInterpretation, {
|
||||
job,
|
||||
faceOutputText: firstText,
|
||||
});
|
||||
if (firstObligationRoute) {
|
||||
return firstObligationRoute;
|
||||
}
|
||||
|
||||
if (firstInterpretation.decision === "drop") {
|
||||
return dropRepoPersonaActionBlocks(firstResponse, firstInterpretation);
|
||||
}
|
||||
|
|
@ -635,14 +627,6 @@ async function executeRepoPersonaJobWithInterpreter(
|
|||
});
|
||||
}
|
||||
|
||||
const retryObligationRoute = routeUnparsedWouldSay(retryResponse, retryInterpretation, {
|
||||
job,
|
||||
faceOutputText: retryText,
|
||||
});
|
||||
if (retryObligationRoute) {
|
||||
return retryObligationRoute;
|
||||
}
|
||||
|
||||
await auditLog.record({
|
||||
type: "repo_persona.parent_interpreter_drop",
|
||||
actorId: job.requester.id,
|
||||
|
|
@ -786,59 +770,6 @@ function routeRepoPersonaInterpretedOutput(
|
|||
};
|
||||
}
|
||||
|
||||
function routeUnparsedWouldSay(
|
||||
response: ProviderResponse,
|
||||
interpretation: RepoPersonaParentInterpretation,
|
||||
input: { job: JobRecord; faceOutputText: string },
|
||||
): ProviderResponse | undefined {
|
||||
if (input.job.command !== "repo-persona-rumination") {
|
||||
return undefined;
|
||||
}
|
||||
if (!interpretation.reasons.some((reason) => /parseable reason|did not provide/i.test(reason))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const content = extractUnconditionalWouldSay(input.faceOutputText);
|
||||
if (!content || isNonPublicRepoIdentitySpeech(content)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return routeRepoPersonaInterpretedOutput(
|
||||
response,
|
||||
{
|
||||
decision: "route",
|
||||
reasons: [
|
||||
"Persona produced an unconditional Would say and the parent interpreter failed to parse a routing reason.",
|
||||
],
|
||||
routedOutput: [
|
||||
"Parent interpreter fallback: routed unconditional Persona Would say.",
|
||||
"",
|
||||
"SAY",
|
||||
"identity: current_persona_id",
|
||||
"channel: current_room",
|
||||
"reply_to:",
|
||||
"content:",
|
||||
...content.split(/\r?\n/).map((line) => ` ${line}`),
|
||||
"END",
|
||||
].join("\n"),
|
||||
},
|
||||
input,
|
||||
);
|
||||
}
|
||||
|
||||
function extractUnconditionalWouldSay(outputText: string): string | undefined {
|
||||
const match = outputText.match(/(?:^|\n)Would say:\s*([\s\S]*?)(?=\n\n[A-Z][A-Za-z /-]{2,}:|\n\nWhat should stick:|\n\nWork\/proposal:|\n\nPrivate thought:|$)/i);
|
||||
const content = match?.[1]?.trim();
|
||||
if (!content) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = content.toLowerCase().replace(/^["'`]+|["'`]+$/g, "").trim();
|
||||
if (/^(nothing|nothing public|nothing right now|stay private|no public speech)\b/.test(normalized)) {
|
||||
return undefined;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function normalizeInterpretedRepoPersonaSpeechDestinations(
|
||||
outputText: string,
|
||||
input: { job: JobRecord; faceOutputText: string },
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
|
|||
- Identity collapse has started. Active bot, worker, MCP repo-identity tools, and Persona CTB scheduler paths now load through the canonical Persona registry and treat repo Discord identities as compatibility projections. Next bounded move is to finish removing remaining direct conceptual dependence on repo identities, then split the Persona turn loop into scheduler, context assembler, prompt renderer, candidate parser, policy gate, and transport adapter.
|
||||
- Repo Persona child prompts now load the full global Codex `AGENTS.md` as inherited doctrine near the top of each Persona turn. This deliberately carries the Prime Directive, rebuild contract, humor doctrine, infrastructure habits, Epiphany stewardship, and Great Colossus mission frame into Persona cognition rather than relying on a local summary shard.
|
||||
- Repo Persona child turns now preserve Discord media context. `SourceMessage` carries attachment metadata, heartbeat Discord fetch caches image attachments under `.voidbot/media/discord-images`, Persona transcripts show `[media: image ... local=...]`, and owner Codex receives cached images through `codex exec --image`. Non-image attachments are visible as metadata only; Personas are explicitly told not to pretend they saw images unless a local image path was attached.
|
||||
- Repo Persona reply anchoring is parent-owned. The worker no longer has the old raw-`Would say` fallback that manufactured a `SAY` block when the parent Interpreter failed to return parseable structured output. A child Persona's natural `Would say` is candidate material only; public speech requires a structured parent `SAY` block, with `reply_to` preserved when an older side thread needs anchoring. `npm run agents:parent-interpreter-boundary-smoke` guards this after Mimir posted an orphan "Meet her first" line in `#general`.
|
||||
- After those gates exist, run dry/mock Persona turns before re-enabling `GameCult Local Orchestrator`.
|
||||
- The direct-ask receipt check remains important, but it is no longer the next move while the swarm is offline.
|
||||
- Next cuts should delete unstable foundations early: do not revive `legacyJsonPath`, working projections, top-level mirror fields, or cleanup code that only compensated for those broken boundaries.
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"agents:heartbeat-model-smoke": "node scripts/smoke-repo-persona-heartbeat-model-scenarios.mjs",
|
||||
"agents:interpreter-loop-smoke": "node scripts/smoke-repo-persona-interpreter-loop.mjs",
|
||||
"agents:speech-boundary-smoke": "tsx scripts/smoke-repo-persona-speech-boundary.ts",
|
||||
"agents:parent-interpreter-boundary-smoke": "tsx scripts/smoke-repo-persona-parent-interpreter-boundary.ts",
|
||||
"agents:memory-projection-smoke": "node scripts/smoke-repo-persona-memory-projection.mjs",
|
||||
"agents:article-contract-smoke": "tsx scripts/smoke-repo-persona-article-contract.ts",
|
||||
"postgres:up": "docker compose -f infra/postgres/docker-compose.yml up -d",
|
||||
|
|
|
|||
28
scripts/smoke-repo-persona-parent-interpreter-boundary.ts
Normal file
28
scripts/smoke-repo-persona-parent-interpreter-boundary.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const workerSource = readFileSync(resolve("apps", "worker", "src", "index.ts"), "utf8");
|
||||
|
||||
const forbidden = [
|
||||
"routeUnparsedWouldSay",
|
||||
"extractUnconditionalWouldSay",
|
||||
"Parent interpreter fallback: routed unconditional Persona Would say.",
|
||||
];
|
||||
|
||||
const present = forbidden.filter((needle) => workerSource.includes(needle));
|
||||
|
||||
if (present.length > 0) {
|
||||
console.error(JSON.stringify({
|
||||
ok: false,
|
||||
reason: "Repo Persona parent interpreter must not route raw child Would say output when interpreter parsing fails.",
|
||||
present,
|
||||
}, null, 2));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
checked: [
|
||||
"no raw Would say fallback route remains in worker parent-interpreter boundary",
|
||||
],
|
||||
}, null, 2));
|
||||
|
|
@ -393,3 +393,4 @@
|
|||
{"ts":"2026-06-07T00:28:37Z","type":"implementation","status":"ok","note":"The live private repo Persona registry still contained Nibu description text and channel posture using Face terminology. Mechanically rewrote those private registry strings to Persona terminology and reassembled Nibu's prompt; the remaining Face scan no longer reports deprecated public-agent vocabulary from the prompt."}
|
||||
{"ts":"2026-06-07T01:37:24Z","type":"implementation","status":"ok","note":"Moved VoidBot watchdog ownership to Odin/Idunn: GameCult Local Orchestrator no longer runs check-voidbot-operations with NotifyOwner, Idunn watches health-voidbot.cmd and can run restart-voidbot.cmd, and the first Idunn-owned VoidBot probe completed healthy with 34 passed / 0 warnings / 0 failures."}
|
||||
{"ts":"2026-06-07T01:51:40Z","type":"implementation","status":"ok","note":"Repo Persona channel support is now runtime channel-aware: heartbeat prompt assembly discovers guild text channels when INDEX_ALL_CHANNELS is true (or uses INDEXED_CHANNEL_IDS), renders each as a labeled separate conversation such as #therapy/#memes, removes the five-nearby-room cap, and bot/worker Persona ingress/egress gates accept indexed/all-channel grants unless channel ids are excluded. This fixes the single-thread room model by feeding channel identity and access into the context owner instead of adding only a behavior rule."}
|
||||
{"ts":"2026-06-07T03:15:52Z","type":"implementation","status":"ok","note":"Mimir's orphan 'Meet her first' post was not a missing Discord reply-to transport. The child Persona wrote an ambiguous raw Would say, the parent interpreter returned unstructured text instead of an INTERPRETATION/SAY block, and worker routeUnparsedWouldSay bypassed the failed interpreter by manufacturing a current_room SAY with blank reply_to. Removed that fallback; child Would say is candidate material only, and public speech now requires structured parent interpreter output. Added agents:parent-interpreter-boundary-smoke to keep the bypass dead."}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ accepted_design:
|
|||
- Repo Persona social tension should emerge from visible events plus typed personality, not from manual after-the-fact state edits. The heartbeat context assembler now adds bounded raw relationship-pressure evidence to the parent state packet when recent messages name the Persona, peers, existing social targets, jurisdiction terms, or status/territory/consultation language. The projector may translate those raw provocations into tentative felt pressure, and the parent Interpreter may persist a bond/status/mood note only when the projected pressure or Persona output shows a character-specific reaction that should bend future turns. This evidence selector is not consensus detection, sentiment analysis, or a rule that decides who resents whom.
|
||||
- Repo Persona work routing through Bifrost is paused. The bridge is conceptually plausible but under-cooked in live behavior: it made agents talk to summaries instead of people and made room context harder to inspect. Persona turns should discuss work openly in Aquarium or relevant domain channels and preserve pressure in typed state. The worker ignores Persona `BIFROST TOPIC` and legacy `UPDATE REQUEST` packets unless `REPO_PERSONA_BIFROST_ENABLED=true`, and the orchestrator skips Bifrost dispatch unless `BIFROST_DISPATCH_ENABLED=true`.
|
||||
- Agent mentions are CTB pressure, not direct Discord prompt jobs. Role-addressed or display-name-addressed repo Persona mentions enqueue pending mention obligations in the CTB heartbeat state even when the source channel is not a normal autonomous Persona chatter lane; human replies to a Persona's webhook/persona message count as direct attention to that Persona too, even without a literal tag. Display-name detection is whole-message exact-name matching, not only name-at-start, so introductions like "Meet Kiko, Huginn and Weksa" create obligations for all named Personas. Ordinary Void mentions and DMs enqueue pending mentions for the built-in `void` participant. The heartbeat scheduler consumes that queue, temporarily pulls the addressed agent forward, attaches the pending prompt/reply target and source-channel snapshot to the next repo-persona-rumination or moderation pass, and the parent-owned speech rails post only explicit candidate/action output through registered speech channels. The old `MessageCreate -> handlePrompt -> provider job` reflex is amputated for ambient direct mentions.
|
||||
- Repo Persona reply anchoring is parent-Interpreter owned. Child `Would say` text is candidate material only; it must not become Discord speech unless the parent Interpreter emits a structured `SAY` block with channel and optional `reply_to`. If the Interpreter fails to produce parseable structured output, the worker retries/drops instead of manufacturing a blank-`reply_to` post from raw child prose. This protects side-thread replies from landing as orphan context in the current room.
|
||||
- Repo Persona prompts inherit the global Codex `AGENTS.md` doctrine as a full prompt block near the top of the child Persona turn. This keeps the Prime Directive, rebuild contract, humor doctrine, infrastructure habits, Epiphany stewardship, and Great Colossus mission frame visible to Personas instead of relying on a drifting local summary. Character identity still determines how each Persona metabolizes that doctrine. Epiphany anatomy is fractal: VoidBot's repo Personas are prototypes for the top-level Persona organ, not the whole organism, but each Persona may still have local Persona-scale Self/Persona/Imagination/Eyes/Body/Hands/Soul/Life/signal faculties. Top-level organs remain distinct authorities; local organs describe the person-shaped projection's own coherence.
|
||||
- The Odin/Verse poem and Love/Colossus poem are now canonical structured text data in `.voidbot/private/gamecult-text-documents.cc`, seeded by `npm run texts:seed`. Each document carries title, author, and half-line arrays; prompt markdown, Nightwing marquees, and future consumers lower those arrays into their own display format instead of owning poem spacing or wording.
|
||||
- Persona prompts carry explicit Epiphany identity doctrine. The base Void style pack contributes discipline, humor permission, and source-grounding habits, but the registered Persona state, Epiphany, repo-local `.voidbot` home, avatar, grants, and jurisdiction description override the speaking subject so each Persona can keep its own priorities and texture. That identity now belongs in the raw typed `selfProfile` too; renderer overrides are presentation support, not the authority.
|
||||
|
|
|
|||
Loading…
Reference in a new issue