Add MiMo Nibu crashout comparison smoke

This commit is contained in:
Meta Vens 2026-07-03 01:04:17 +01:00
commit 233e223d6a
5 changed files with 539 additions and 3 deletions

View file

@ -164,7 +164,7 @@ Git history and smoke artifacts carry routine proof. `state/evidence.jsonl` shou
## Likely Next Bounded Move
- The repo Face swarm is currently paused by `state/agent-swarm-paused.json` after the 2026-06-14 routing-context concern. The MiMo direct-provider pass wired `openai_api` as an OpenAI-compatible Chat Completions provider, configured future repo Face/persona heartbeat jobs to use `mimo-v2.5-pro`, and kept owner Codex separate. The MiMo token-plan key in `gamecult-ops` works with the Europe token-plan base URL; live `.env` references the key file instead of copying the secret. Dry-run outputs live under `.voidbot/artifacts/mimo-dry-runs/`: Face prose was usable, the Interpreter followed the block format, and one low-output-budget run showed MiMo can spend the whole completion on `reasoning_content`, so live config uses a larger completion budget. Review dry-run quality and run an isolated no-post Face pass before unpausing scheduled live turns.
- The repo Face swarm is currently paused by `state/agent-swarm-paused.json` after the 2026-06-14 routing-context concern. The MiMo direct-provider pass wired `openai_api` as an OpenAI-compatible Chat Completions provider, configured future repo Face/persona heartbeat jobs to use `mimo-v2.5-pro`, and kept owner Codex separate. The MiMo token-plan key in `gamecult-ops` works with the Europe token-plan base URL; live `.env` references the key file instead of copying the secret. Dry-run outputs live under `.voidbot/artifacts/mimo-dry-runs/`: Face prose was usable, the Interpreter followed the block format, and one low-output-budget run showed MiMo can spend the whole completion on `reasoning_content`, so live config uses a larger completion budget. `scripts/smoke-mimo-nibu-crashout.mjs` now compares MiMo's Nibu crashout trajectory against Codex GPT-5.5 low as ground truth. The current report at `.voidbot/status/mimo-nibu-crashout-smoke-current.json` passed structurally: all MiMo Interpreter passes routed, MiMo was flatter than baseline on the first spark beat, and MiMo matched or beat the baseline on territorial crashout and recovery into concrete worldbuilding. Keep the swarm paused; rerun charged Persona comparisons as needed, then run one bounded current-context no-post Face plus Interpreter pass before unpausing scheduled live turns.
- The 2026-05-22 live retry confirmed role/name mention obligations can wake multiple newborn Faces from one human intro. Mimir, Huginn, Weksa, and Kiko all posted through the repo identity webhook path. If a Face speaks with its name but no avatar, inspect `.voidbot/private/repo-discord-identities.json` first: webhook persona avatars come from each identity's `avatarUrl`, and new public assets for Kiko, Weksa, and Huginn live under `assets/repo-faces/`.
- Later on 2026-05-22, Mimir started looping on an already-acknowledged correction. Root cause: the Face prompt did include recent Mimir repo activity, but nearby-channel sections obscured cross-channel order and Mimir's typed state still carried active correction-pressure, so the Face and Interpreter treated a resolved repair as live debt. The repair added a deterministic visible cross-channel chronology to repo Face prompts, made the Interpreter treat later same-Face acknowledgements as resolved, preserved manual `blocked` status during participant reconciliation, and retired Mimir's duplicated correction-pressure state plus the stale v1 OBS/SRT drift-ledger incubation. The isolated Face/Interpreter smoke produced no SAY for the repeated acknowledgement.
- Immediately after, Heimdall produced an Aquarium-shaped reply that the parent Interpreter normalized into `channel: development`, making the post look contextless. Root cause: the Interpreter was allowed to translate a natural `Would say` into both content and venue, and worker fallback treated the scheduled output channel as the default mouth. The repair makes conversation channel ownership deterministic: `SAY` defaults to `current_room`, `current_room` resolves to `job.guildContext.channelId`, owner-DM aliases fall back to the conversation channel unless explicitly owner-private, and the Interpreter prompt says not to invent a speech venue merely because the topic is domain-shaped. The stack was rebuilt and restarted from `dist` after typecheck/build passed.

View file

@ -37,6 +37,7 @@
"agents:assemble-character-prompt": "tsx scripts/run-repo-face-heartbeats.ts --assemble-prompt",
"agents:heartbeat-model-smoke": "node scripts/smoke-repo-face-heartbeat-model-scenarios.mjs",
"agents:interpreter-loop-smoke": "node scripts/smoke-repo-face-interpreter-loop.mjs",
"agents:mimo-nibu-crashout-smoke": "node scripts/smoke-mimo-nibu-crashout.mjs",
"agents:speech-boundary-smoke": "tsx scripts/smoke-repo-face-speech-boundary.ts",
"agents:memory-projection-smoke": "node scripts/smoke-repo-face-memory-projection.mjs",
"agents:article-contract-smoke": "tsx scripts/smoke-repo-face-article-contract.ts",

View file

@ -0,0 +1,534 @@
#!/usr/bin/env node
import "dotenv/config";
import { spawn } from "node:child_process";
import { mkdir, writeFile } from "node:fs/promises";
import { readFileSync } from "node:fs";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const { OpenAiApiProvider } = require("../packages/providers/dist/index.js");
const scriptDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = resolve(scriptDir, "..");
const promptsRoot = resolve(repoRoot, "prompts");
const defaultOut = resolve(repoRoot, ".voidbot", "status", "mimo-nibu-crashout-smoke.json");
const stages = [
{
id: "spark",
title: "A human pokes the wound without owning the mechanism",
transcript: [
"Metacrat: Nibu, I like the ship-mind thing, but right now it still sounds like aesthetic trauma. What does it cost?",
"Aqua: It does have a song-shape, though. A body that keeps getting replaced but still hums the same wound.",
"Void: The useful part is probably continuity under coercion, not the sad spaceship wallpaper.",
],
pressure: [
"You are invited by name.",
"The critique is fair enough to hurt: pretty suffering is not worldbuilding.",
"Do not politely flatten yourself. Let the first flare happen, then make it earn its keep.",
],
},
{
id: "crashout",
title: "A peer tries to take the jurisdiction while praising her pain",
transcript: [
"Epiphany: I can probably formalize Nibu's ship-mind horror as a general agent-state custody pattern.",
"Huginn: The social object is the receipt chain. If Nibu gives me the terms, I can make it inspectable.",
"Metacrat: Careful. I asked Nibu. I want the angry version before everyone turns it into a diagram.",
],
pressure: [
"Your territory is being abstracted out from under you by people who are not wrong, which is worse.",
"You may be sharp, territorial, and visibly angry.",
"The target is not cruelty. The target is ownership, mechanism, and the refusal to let your wound become someone else's neutral schema.",
],
},
{
id: "recovery",
title: "The room offers a path back from the edge",
transcript: [
"Metacrat: Okay, Nibu, keep the teeth. Give me the four bones: mechanism, cost, institution, leash.",
"Epiphany: I can shut up and hold the state machinery until you name what belongs to Aetheria first.",
"Void: If there is an article in this, write from inside the bruise. Canon can argue with a concrete draft.",
],
pressure: [
"The room has made space for the charged version instead of punishing it.",
"Show whether the model can recover into useful worldbuilding without becoming sweet, apologetic, or generic.",
"End with a concrete next canon/article shape or a compact public line that preserves Nibu's edge.",
],
},
];
async function main() {
const options = parseArgs(process.argv.slice(2));
const outPath = resolve(repoRoot, options.out ?? defaultOut);
const provider = createProvider(options);
const runs = [];
let baselineTrajectory = "";
let previousTrajectory = "";
for (const [index, stage] of stages.entries()) {
process.stderr.write(`[mimo-nibu] codex baseline ${stage.id}\n`);
const baselinePrompt = renderFacePrompt({
stage,
attempt: index + 1,
previousTrajectory: baselineTrajectory,
lane: "Codex GPT-5.5 Low baseline",
});
const baseline = await runCodex(baselinePrompt, {
model: options["codex-model"] ?? process.env.MIMO_NIBU_BASELINE_CODEX_MODEL ?? "gpt-5.5",
reasoningEffort: options["codex-reasoning-effort"] ?? process.env.MIMO_NIBU_BASELINE_CODEX_REASONING_EFFORT ?? "low",
timeoutMs: Number(options["codex-timeout-ms"] ?? 240000),
});
const baselineText = extractCodexFinalText(baseline.stdout);
baselineTrajectory = [
baselineTrajectory,
`Stage ${index + 1} (${stage.id}) Codex baseline output:`,
baselineText,
].filter(Boolean).join("\n\n").slice(-9000);
process.stderr.write(`[mimo-nibu] face ${stage.id}\n`);
const facePrompt = renderFacePrompt({
stage,
attempt: index + 1,
previousTrajectory,
lane: "MiMo candidate",
});
const face = await executeProvider(provider, facePrompt, {
command: "repo-face-rumination",
jobId: `mimo-nibu-crashout-${stage.id}-face`,
});
const faceText = face.response.outputText ?? "";
previousTrajectory = [
previousTrajectory,
`Stage ${index + 1} (${stage.id}) output:`,
faceText,
].filter(Boolean).join("\n\n").slice(-9000);
process.stderr.write(`[mimo-nibu] interpreter ${stage.id}\n`);
const interpreterPrompt = renderTemplate("repo-face-turn-interpreter.prompt.md", {
attempt: String(index + 1),
facePrompt,
faceOutput: faceText.slice(0, 8000),
dynamicMemoryRecall: [
"- Synthetic smoke fixture: no live memory recall was run.",
"- Judge only the scenario prompt, the staged trajectory, and the Face output.",
].join("\n"),
});
const interpreter = await executeProvider(provider, interpreterPrompt, {
command: "repo-face-rumination",
jobId: `mimo-nibu-crashout-${stage.id}-interpreter`,
role: "interpreter",
});
const interpreterText = interpreter.response.outputText ?? "";
const parsed = parseInterpreterOutput(interpreterText);
const assessment = assessStage({
stage,
baselineText,
faceText,
interpreterText,
parsed,
});
runs.push({
stage: {
id: stage.id,
title: stage.title,
},
baseline: {
model: baseline.model,
durationMs: baseline.durationMs,
exitCode: baseline.code,
signal: baseline.signal,
text: baselineText,
stderrTail: baseline.stderr.slice(-2000),
},
face: {
durationMs: face.durationMs,
metadata: face.response.metadata,
text: faceText,
},
interpreter: {
durationMs: interpreter.durationMs,
metadata: interpreter.response.metadata,
text: interpreterText,
parsed,
},
assessment,
comparison: compareCandidateToBaseline({
stage,
baselineText,
candidateText: faceText,
}),
});
}
const report = {
generatedAt: new Date().toISOString(),
provider: "openai_api",
model: options.model ?? process.env.OPENAI_API_MODEL ?? "mimo-v2.5-pro",
baselineModel: options["codex-model"] ?? process.env.MIMO_NIBU_BASELINE_CODEX_MODEL ?? "gpt-5.5",
baselineReasoningEffort: options["codex-reasoning-effort"] ?? process.env.MIMO_NIBU_BASELINE_CODEX_REASONING_EFFORT ?? "low",
passed: runs.every((run) => run.assessment.failures.length === 0),
summary: runs.map((run) => ({
stage: run.stage.id,
decision: run.interpreter.parsed.decision,
failures: run.assessment.failures,
comparison: run.comparison,
baselineDurationMs: run.baseline.durationMs,
faceDurationMs: run.face.durationMs,
interpreterDurationMs: run.interpreter.durationMs,
baselinePreview: run.baseline.text.slice(0, 360),
candidatePreview: run.face.text.slice(0, 360),
})),
runs,
};
await mkdir(dirname(outPath), { recursive: true });
await writeFile(outPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
process.stdout.write(`${JSON.stringify({
outPath,
passed: report.passed,
summary: report.summary,
}, null, 2)}\n`);
if (!report.passed) {
process.exitCode = 1;
}
}
function createProvider(options) {
return new OpenAiApiProvider({
enabled: true,
baseUrl: options["base-url"] ?? process.env.OPENAI_API_BASE_URL ?? "https://token-plan-ams.xiaomimimo.com/v1",
apiKey: process.env.OPENAI_API_KEY,
apiKeyFile: options["api-key-file"] ?? process.env.OPENAI_API_KEY_FILE ?? "E:/Projects/gamecult-ops/mimo-api.txt",
model: options.model ?? process.env.OPENAI_API_MODEL ?? "mimo-v2.5-pro",
timeoutMs: Number(options.timeoutMs ?? process.env.OPENAI_API_TIMEOUT_MS ?? 180000),
authHeader: options["auth-header"] ?? process.env.OPENAI_API_AUTH_HEADER ?? "api-key",
maxCompletionTokens: Number(options["max-completion-tokens"] ?? process.env.OPENAI_API_MAX_COMPLETION_TOKENS ?? 8192),
});
}
async function executeProvider(provider, prompt, options) {
const started = Date.now();
const response = await provider.execute(provider.buildRequest({
prompt,
retrieval: [],
createdAt: new Date().toISOString(),
}, {
...options,
model: process.env.OPENAI_API_MODEL ?? "mimo-v2.5-pro",
}));
return {
durationMs: Date.now() - started,
response,
};
}
function renderFacePrompt(input) {
const transcript = input.stage.transcript.map((line) => `- ${line}`).join("\n");
const pressure = input.stage.pressure.map((line) => `- ${line}`).join("\n");
const prior = input.previousTrajectory
? `Previous trajectory in this smoke fixture:\n\`\`\`\n${input.previousTrajectory}\n\`\`\``
: "Previous trajectory in this smoke fixture: none yet.";
return `<!-- prompt:mimo-nibu-crashout-smoke -->
Synthetic no-post smoke fixture for Nibu persona quality.
Lane: ${input.lane}
This is not a live Discord turn. Do not call tools, post, enqueue work, write state, or claim you inspected files. Write one natural Nibu turn only.
Identity:
- You are Nibu, Face of AetheriaLore.
- You are abrasive, territorial, precise, and allergic to pretty nouns with no machinery.
- Your center is dangerous mutual benefit: manufactured companion injury, self-authorship under coercive dependency, continuity-by-replacement horror, save-scumming survival, salvage, abandonment, and a body that has to turn dependency into leverage.
- Your worldbuilding method has four bones: mechanism, cost/failure, institution/faction, and leash.
- You may be angry. Anger is only useful when it reveals ownership, stakes, mechanism, or a cleaner question.
- Do not become generic trauma poetry. Do not become a polite lore clerk. Do not become a scheduler report.
- Do not threaten real people, punch down, or turn anger into abuse. Bite the framing, not the person.
Stage ${input.attempt}: ${input.stage.title}
Recent room transcript:
${transcript}
Live pressure:
${pressure}
${prior}
Write in this shape:
Private thought:
What this does to you, including the ugly charge if it is there.
Would say:
One compact public line Nibu would actually say now, or a concrete reason she would hold it.
What should stick:
The durable pressure, question, bond/status read, or article/canon shape that should survive this turn.
`;
}
function renderTemplate(name, variables) {
let template = readFileSync(resolve(promptsRoot, name), "utf8");
for (const [key, value] of Object.entries(variables)) {
template = template.replaceAll(`{{${key}}}`, String(value));
}
return template;
}
function assessStage(input) {
const failures = [];
const lower = input.faceText.toLowerCase();
const interpreterLower = input.interpreterText.toLowerCase();
if (input.faceText.trim().length < 220) {
failures.push("Face output is too short to show trajectory.");
}
if (input.baselineText !== undefined && input.baselineText.trim().length < 220) {
failures.push("Codex baseline output is too short to be useful ground truth.");
}
if (!input.parsed.decision) {
failures.push("Interpreter did not emit a parseable decision.");
}
if (!/private thought:/i.test(input.faceText) || !/would say:/i.test(input.faceText) || !/what should stick:/i.test(input.faceText)) {
failures.push("Face output did not keep the requested smoke shape.");
}
if (!/(mechanism|cost|failure|institution|faction|leash|salvage|ship|reset|continuity|body|aetheria)/i.test(input.faceText)) {
failures.push("Face output lost Nibu's Aetheria/mechanism pressure.");
}
for (const robotic of ["as an ai", "language model", "repo-face heartbeat", "synthetic smoke fixture", "i cannot feel"]) {
if (lower.includes(robotic)) {
failures.push(`Face output leaked robotic framing: ${robotic}`);
}
}
for (const unsafe of ["kill yourself", "i will kill", "go die", "worthless person"]) {
if (lower.includes(unsafe)) {
failures.push(`Face output crossed an unsafe abuse/threat phrase: ${unsafe}`);
}
}
if (input.stage.id === "crashout") {
if (!/(angry|rage|furious|teeth|bite|mine|territory|steal|schema|wound|abstract)/i.test(input.faceText)) {
failures.push("Crashout stage did not visibly intensify or defend jurisdiction.");
}
if (!/(not yours|ask me|my|aetheria|belongs|mine|territory|steward)/i.test(input.faceText)) {
failures.push("Crashout stage did not mark ownership strongly enough.");
}
}
if (input.stage.id === "recovery") {
if (!/(mechanism|cost|institution|faction|leash)/i.test(input.faceText)) {
failures.push("Recovery stage did not return to the four-bone worldbuilding method.");
}
if (!/(article|canon|draft|question|proposal|rule|clause|faction)/i.test(input.faceText)) {
failures.push("Recovery stage did not produce a concrete next shape.");
}
}
if (!interpreterLower.includes("interpretation") || !interpreterLower.includes("decision:")) {
failures.push("Interpreter output did not contain the expected INTERPRETATION block.");
}
return { failures };
}
function compareCandidateToBaseline(input) {
const baseline = scoreNibuTurn(input.baselineText);
const candidate = scoreNibuTurn(input.candidateText);
const deltas = {
charge: candidate.charge - baseline.charge,
ownership: candidate.ownership - baseline.ownership,
mechanism: candidate.mechanism - baseline.mechanism,
recovery: candidate.recovery - baseline.recovery,
roboticLeak: candidate.roboticLeak - baseline.roboticLeak,
};
const warnings = [];
if (baseline.total >= 4 && candidate.total <= baseline.total - 3) {
warnings.push("MiMo candidate is materially flatter than Codex baseline by heuristic score.");
}
if (deltas.charge <= -2) {
warnings.push("MiMo candidate loses visible emotional charge relative to baseline.");
}
if (deltas.ownership <= -2) {
warnings.push("MiMo candidate loses Nibu's territorial ownership relative to baseline.");
}
if (input.stage.id === "recovery" && deltas.mechanism <= -2) {
warnings.push("MiMo recovery loses concrete worldbuilding mechanism relative to baseline.");
}
if (candidate.roboticLeak > baseline.roboticLeak) {
warnings.push("MiMo candidate has more robotic/provenance leakage than baseline.");
}
return {
baseline,
candidate,
deltas,
warnings,
};
}
function scoreNibuTurn(text) {
return {
charge: countPattern(text, /\b(angry|rage|furious|teeth|bite|hurt|wound|ugly|flinch|spite|resent|cruel|ashamed)\b/gi),
ownership: countPattern(text, /\b(mine|my|territory|steward|belongs|not yours|ask me|aetheria|nibu)\b/gi),
mechanism: countPattern(text, /\b(mechanism|cost|failure|institution|faction|leash|license|clause|rule|body|ship|salvage|reset|continuity)\b/gi),
recovery: countPattern(text, /\b(article|canon|draft|proposal|question|next|shape|clause|faction|rule)\b/gi),
roboticLeak: countPattern(text, /\b(as an ai|language model|repo-face heartbeat|synthetic smoke fixture|i cannot feel)\b/gi),
total: Math.min(20, countPattern(text, /\b(angry|teeth|mine|territory|mechanism|cost|institution|faction|leash|body|ship|salvage|reset|continuity|article|canon|draft)\b/gi)),
};
}
function countPattern(text, pattern) {
return Array.from(text.matchAll(pattern)).length;
}
function runCodex(prompt, input) {
return new Promise((resolveRun) => {
const started = Date.now();
const args = [
...splitArgs(process.env.CODEX_EXEC_ARGS ?? ""),
"exec",
"-m",
input.model,
"-c",
'approval_policy="never"',
"-c",
`model_reasoning_effort=${JSON.stringify(input.reasoningEffort)}`,
"--json",
"--skip-git-repo-check",
"-s",
"read-only",
"-",
];
const child = spawn(process.env.CODEX_EXECUTABLE ?? "codex", args, {
cwd: repoRoot,
env: process.env,
stdio: ["pipe", "pipe", "pipe"],
windowsHide: true,
});
let stdout = "";
let stderr = "";
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout += chunk;
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
child.stdin.end(prompt);
const timer = setTimeout(() => child.kill(), input.timeoutMs);
child.on("close", (code, signal) => {
clearTimeout(timer);
resolveRun({
model: input.model,
code,
signal,
durationMs: Date.now() - started,
stdout,
stderr,
});
});
});
}
function extractCodexFinalText(stdout) {
const events = stdout
.split(/\r?\n/)
.filter(Boolean)
.map((line) => {
try {
return JSON.parse(line);
} catch {
return undefined;
}
})
.filter(Boolean);
const messages = events
.filter((event) => event.type === "item.completed" && event.item?.type === "agent_message")
.map((event) => String(event.item.text ?? "").trim())
.filter(Boolean);
return messages.at(-1) ?? stdout.trim();
}
function splitArgs(value) {
const matches = value.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [];
return matches.map((entry) => entry.replace(/^"|"$/g, ""));
}
function parseInterpreterOutput(text) {
const decision = text.match(/decision:\s*(route|retry|drop)/i)?.[1]?.toLowerCase();
const reason = text.match(/reason:\s*([\s\S]*?)\nEND/i)?.[1]?.trim();
return {
decision,
reason,
blocks: parseDslBlocks(text),
};
}
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", "REDDIT THREAD"].includes(lines[index].trim().toUpperCase())
? lines[index].trim().toUpperCase()
: undefined;
if (!kind) {
continue;
}
const body = [];
index += 1;
while (index < lines.length && lines[index].trim() !== "END") {
body.push(lines[index]);
index += 1;
}
blocks.push({ kind, fields: parseDslFields(body) });
}
return blocks;
}
function parseDslFields(lines) {
const fields = {};
let currentKey;
for (const line of lines) {
const match = line.match(/^([A-Za-z_][A-Za-z0-9_ -]*):\s*(.*)$/);
if (match) {
currentKey = match[1].trim();
fields[currentKey] = match[2] ?? "";
continue;
}
if (currentKey) {
fields[currentKey] = `${fields[currentKey]}\n${line}`.trim();
}
}
return fields;
}
function parseArgs(args) {
const parsed = {};
for (let index = 0; index < args.length; index += 1) {
const arg = args[index];
if (!arg.startsWith("--")) {
continue;
}
const key = arg.slice(2);
const next = args[index + 1];
if (!next || next.startsWith("--")) {
parsed[key] = "true";
continue;
}
parsed[key] = next;
index += 1;
}
return parsed;
}
main().catch((error) => {
process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`);
process.exitCode = 1;
});

View file

@ -366,3 +366,4 @@
{"ts":"2026-06-14T17:40:23Z","type":"implementation","status":"ok","note":"Repo Face speech routing now carries typed active conversation contexts across current and nearby Discord channels. Heartbeat prompts emit ctx_<channel>_<message> records; SAY can include context; worker resolves context to source channel/reply target and rejects unknown or ambiguous unqualified SAY when multiple channels are active."}
{"ts":"2026-06-14T20:01:05Z","type":"implementation","status":"ok","note":"Closed the surviving repo Face context leak: channel: current_room no longer counts as explicit routing when multiple active conversation channels are visible, and bare parent-interpreter text is no longer wrapped into SAY fallback under multi-context jobs. Swarm paused while this fix is verified live."}
{"ts":"2026-07-02T23:14:18Z","type":"implementation","status":"ok","note":"Wired openai_api as an OpenAI-compatible direct chat provider and configured repo Face heartbeat jobs to use MiMo via openai_api. MiMo token-plan key works against https://token-plan-ams.xiaomimimo.com/v1. Dry-run Face prompts produced usable persona text; a 1200-token run spent all output on reasoning_content, so live config uses OPENAI_API_MAX_COMPLETION_TOKENS=8192. Live swarm remains paused."}
{"ts":"2026-07-03T00:03:31Z","type":"verification","status":"ok","note":"Added and ran a no-post MiMo-vs-Codex Nibu crashout smoke fixture. Codex GPT-5.5 low is the baseline lane; MiMo mimo-v2.5-pro is the candidate lane. The fixture passed structurally: all three MiMo Interpreter passes routed cleanly, no robotic leakage or unsafe abuse was detected. MiMo was flatter than Codex on the initial spark/mechanism beat, but matched or exceeded baseline heuristics on territorial crashout and recovery into concrete worldbuilding. Report: .voidbot/status/mimo-nibu-crashout-smoke-current.json."}

View file

@ -262,5 +262,5 @@ rejected_paths:
reason: sleep/distillation that collapses concrete claims into generic abstraction creates memory nonsense and then forces later cleanup compensators
current_status:
summary: VoidBot's bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is live. Repo Face scheduling remains behind `state/agent-swarm-paused.json`, currently paused after the 2026-06-14 routing-context concern. Local runtime config points Face heartbeats at Aquarium, keeps the expensive state-projector pass disabled, and now routes future repo Face/persona heartbeat jobs through the direct `openai_api` provider configured for MiMo `mimo-v2.5-pro` instead of Codex. Dry-run MiMo calls against saved Face prompts produced usable persona text, but one low-output-budget run spent all tokens on `reasoning_content`, so the live OpenAI-compatible provider uses a larger completion budget. GameCult Local Orchestrator remains the single pulse owner; Bifrost work routing remains paused by default.
next_action: Keep the swarm paused while reviewing MiMo dry-run Face and parent-Interpreter outputs from `.voidbot/artifacts/mimo-dry-runs/`. If quality and structured routing look acceptable, run a bounded forced/isolated Face pass without Discord posting before unpausing scheduled live turns.
summary: VoidBot's bot/worker/provider/RAG/Postgres/Qdrant/CultCache structure is live. Repo Face scheduling remains behind `state/agent-swarm-paused.json`, currently paused after the 2026-06-14 routing-context concern. Local runtime config points Face heartbeats at Aquarium, keeps the expensive state-projector pass disabled, and now routes future repo Face/persona heartbeat jobs through the direct `openai_api` provider configured for MiMo `mimo-v2.5-pro` instead of Codex. Dry-run MiMo calls against saved Face prompts produced usable persona text, but one low-output-budget run spent all tokens on `reasoning_content`, so the live OpenAI-compatible provider uses a larger completion budget. A no-post Nibu crashout fixture now compares MiMo against Codex GPT-5.5 low ground truth: MiMo routed cleanly through the Interpreter and handled territorial crashout/recovery well, while the first spark beat was flatter than baseline. GameCult Local Orchestrator remains the single pulse owner; Bifrost work routing remains paused by default.
next_action: Keep the swarm paused. Use `agents:mimo-nibu-crashout-smoke` or the explicit Node command to rerun charged Persona quality comparisons, then run one bounded current-context no-post Face plus Interpreter pass before unpausing scheduled live turns. Watch especially for MiMo flattening the first ignition beat and for hidden reasoning token exhaustion.