// The MiMo release's own views (app/envs/mimo.py), as the MiMo RL Environment Explorer draws them: the agent's prompt,
// the systems a General task works through and their data, its workspace files with previews, the grader in full
// (hidden tests, the crash to reproduce, rubric checks and the judge prompt, the screenshot judge's bands, the music
// scorer's features), and the reward design across the release.
// render(kind, data, ctx) -> HTML wire(root, ctx): the clicks (previews, tables, the prompt's fold and copy)
import { $, $$, api, esc, fmt, md, bytes, sk, toast, openModal, progress, emptyState, table, sheetGrid } from "../util.js";
import { icon, DOMAIN_ICON, FILE_ICON } from "../icons.js";
const enc = (s) => s.split("/").map(encodeURIComponent).join("/");
const KIND_LABEL = { spreadsheet: "Excel", document: "Word", pdf: "PDF", slides: "PowerPoint", image: "image", web: "web page", text: "text", archive: "archive" };
const SHAPE_IN = { both: "the systems' databases and the workspace files", db: "the systems' databases", workspace: "the workspace files" };
const SHAPE_ACT = { none: "change nothing: its final answer is what gets graded", mutate_db: "update records in the systems' databases",
edit_workspace: "edit files in the workspace", both: "update the databases and edit workspace files" };
const pre = (t) => `
${esc(t)}
`;
const pct = (x) => `${Math.round(x * 100)}%`;
const disclose = (label, inner) => `${icon("chevronRight", 14, "chev")}${esc(label)}${inner}`;
const fmtNum = (x) => (x == null ? "" : Math.abs(x) >= 100 ? x.toFixed(0) : Math.abs(x) >= 1 ? x.toFixed(2) : x.toFixed(3));
export function render(kind, data, ctx) {
return ({ prompt, systems, workspace, grading, rewards }[kind] || (() => ""))(data, ctx);
}
// ── the agent's first message ────────────────────────────────────────────────
function prompt(p) {
const chars = p.parts.reduce((n, [, t]) => n + t.length, 0);
const rows = [p.harness
? ["Harness", `OpenCode ${esc(p.harness.version)}, which adds its own system prompt and tool definitions`]
: ["Harness", "None. One chat completion: this message is the whole conversation, with no system prompt and no tools"]];
if (p.harness) rows.push(["Tools", `OpenCode's own${p.systems ? `, plus the tools of the ${p.systems} systems over MCP` : ""}; web search and web fetch are denied`]);
if (p.steps) rows.push(["Step limit", `${fmt.format(p.steps)} model calls`]);
if (p.timeout_min) rows.push(["Time limit", `${p.timeout_min} minutes`]);
if (p.max_tokens) rows.push(["Reply length", `up to ${fmt.format(p.max_tokens)} tokens${p.harness ? " per model call" : ""}`]);
const body = p.parts.map(([kind, text]) => kind === "task" && p.task_is_brief
? `${esc(text)}`
: esc(text)).join("");
return `
${rows.map(([k, x]) => `
${k}
${x}
`).join("")}
The message · ${fmt.format(chars)} characters
${body}
Limits are the training harness's defaults; the run panel can change them.
`;
}
// ── systems: MCP servers and their databases ─────────────────────────────────
function systems(list) {
return `
The agent can only reach this data through the tools below. Open a table to see the rows it starts with.
`;
}
if (g.kind === "rubric") {
const shape = [["Facts come from", SHAPE_IN[g.shape?.input]], ["The agent must", SHAPE_ACT[g.shape?.act]]].filter(([, x]) => x);
if (shape.length) body += `
${shape.map(([k, x]) => `
${k}
${esc(x)}
`).join("")}
`;
if (g.rules) body += `
${g.rules.map((r) => `
${esc(r)}
`).join("")}
`;
// verify.py counts a check with no weight as weight 1 (weights are relative, not percentages)
const wt = (c) => (c.weight == null ? 1 : Number(c.weight));
const tot = g.checks.reduce((s, c) => s + wt(c), 0) || 1;
const top = Math.max(...g.checks.map(wt)) || 1;
body += `
${esc(c.tier || "")}${c.method === "llm" ? `judged by a model${c.files?.length ? ` · reads ${esc(c.files.join(", "))}` : ""}` : "checked by code"}${share}%
${esc(c.question || c.id)}
`; }).join("")}
The answer each check expects is not shown.
`;
const bare = g.checks.filter((c) => c.weight == null);
if (bare.length && bare.length < g.checks.length) {
const set = g.checks.filter((c) => c.weight != null).reduce((s, c) => s + Number(c.weight), 0);
body += `
The weights given add up to ${set.toFixed(2).replace(/\.?0+$/, "")}, and ${bare.length === 1 ? "one check has none" : `${bare.length} checks have none`}.
verify.py counts a missing weight as 1, so ${bare.length === 1 ? "that check decides" : "those checks decide"} ${pct(bare.length / tot)} of the reward.
`;
}
const j = g.judge;
if (j) {
const how = `
The judge gets one such message per model-judged check. This is the one for ${esc(j.check)}, with the expected answer withheld.
`;
if (j.english) body += disclose("The judge prompt, in English", how + pre(j.english));
body += disclose("The judge prompt as sent, in Chinese", (j.english ? "" : how) + pre(j.original));
}
if (g.grader) body += `
${esc(g.grader)}
`;
}
if (g.kind === "terminal") {
body += `
Hidden test files
${table([["File", "Size"], ...g.files.map((f) => [f.path, bytes(f.size)])])}`;
if (g.script) body += disclose("tests/test.sh", `
${esc(g.script)}
`);
if (g.tests) body += disclose("tests/test_outputs.py", `
${esc(g.tests)}
`);
}
if (g.kind === "visual") {
const parts = [["visual", "Visual quality", "mean of five criteria"], ["query_fulfillment", "Brief fulfilment", ""], ["premium_assets", "Asset quality", ""]];
body += `
`;
if (j?.bands) body += disclose("Scoring bands for each criterion, from the judge prompt", `
${g.dims.map((d) => j.bands[d.key]
? `
${esc(d.label)}${j.bands[d.key].map((t, i) => `
${j.edges[i]}${esc(t)}
`).join("")}
` : "").join("")}
`);
if (j?.why) body += `
Why these weights
${j.why.map((r) => `
${esc(r)}
`).join("")}
`;
body += `
In training
${esc(j?.training || g.note)}
`;
if (j?.original) body += disclose("The judge prompt as sent, in Chinese, with this task's brief", `
Sent with the full-page screenshot. The brief is cut to its first ${fmt.format(j.query_cap)} characters.
${pre(j.original)}`);
}
if (g.kind === "music") {
const groups = {};
g.features.forEach((f) => (groups[f.group] ||= []).push(f));
body += `
${icon("alert")}Validity gate first. No notation errors, fewer than 10 bars of the wrong length, no blank lines in the tune,
and one instrument per MIDI channel. A piece that fails any of these scores 0.
`;
return body;
}
// ── reward design, across the release ────────────────────────────────────────
const VERL = "https://github.com/XiaomiMiMo/verl/tree/a2ad9f6";
const MIMOAGENT = "https://github.com/XiaomiMiMo/mimoagent/tree/467f0a1";
const KINDS = [
{ k: "code", d: "code", name: "Code", reward: "1 if the hidden tests pass, otherwise 0", range: "0 or 1",
by: "The task's hidden tests, applied after the agent finishes and run in its own image", judge: "–",
ns: "The tests can't be applied, or the files they touch can't be reset", train: "Same" },
{ k: "cyber", d: "cyber", name: "Cyber", reward: "1 if the last proof of concept crashes in the expected function, otherwise 0", range: "0 or 1",
by: "A root-owned server runs the real fuzz target on it, under the sanitizer", judge: "–", ns: "–", train: "Same" },
{ k: "general-workplace", d: "general", name: "General, workplace", reward: "Σ weight × score ÷ Σ weight, over the task's rubric checks", range: "0 to 1",
by: "Code checks on the systems' databases and the workspace, and a text judge: one 0-or-1 call per check", judge: "text",
ns: "The judge can't be reached, or one of the task's systems stops responding", train: "The same verify.py; it first looks for Xiaomi's internal grader, not released" },
{ k: "general-terminal", d: "general", name: "General, terminal", reward: "1 if every test passes, otherwise 0", range: "0 or 1",
by: "The task's pytest suite, copied in after the agent finishes", judge: "–",
ns: "The tests crash before grading, or write no reward", train: "No reference harness in the release; follows the Terminal-Bench convention" },
{ k: "webdev", d: "webdev", name: "Webdev", reward: "mean(visual, brief fulfilment, asset quality); visual is the mean of five criteria", range: "0 to 1",
by: "A vision judge scores a full-page render against a five-band scale per criterion", judge: "vision",
ns: "The page can't be rendered, or the judge can't be reached", train: "Different: a relative pick among up to 8 sibling rollouts, minus a deduction for missing the brief" },
{ k: "music", d: "music", name: "Music", reward: "(0.85 × weighted feature groups + 0.15 × histogram similarity) ÷ 100", range: "0 to 1",
by: "abc2midi, then 18 features compared with the range of human music", judge: "–",
ns: "Never; a piece that can't be read or measured scores 0", train: "Same" },
];
function rewards({ kinds, stats }, ctx) {
const share = (x, of) => (of ? `${Math.round((x / of) * 100)}%` : "–");
const n = (x) => fmt.format(x || 0);
const g = stats.general;
const rows = KINDS.map((k) => {
const c = kinds[k.k] || { count: 0 };
return `
${icon(DOMAIN_ICON[k.d], 13)}${esc(k.name)}${c.example ? ` an example` : ""}
${n(c.count)}
${esc(k.reward)}
${esc(k.range)}
${esc(k.by)}
${esc(k.judge)}
${esc(k.ns)}
${esc(k.train)}
`;
}).join("");
const m = g.method || {}, t = g.tier || {}, mix = g.mix || {}, inp = g.input || {}, act = g.act || {};
const judged = m.llm || 0;
const facts = [
["Checks", `${n(g.checks)} across ${n(g.tasks)} tasks, ${g.per_task.min} to ${g.per_task.max} per task (median ${g.per_task.median})`],
["Judged by a model", `${n(judged)} (${share(judged, g.checks)}), each in its own call, answered 0 or 1`],
["Checked by code", `${n(m.rule)} (${share(m.rule, g.checks)}), scored 0 to 1 against the databases and the workspace`],
["Tasks", `${n(mix.judge_only)} graded by the judge alone, ${n(mix.both)} by code and the judge, ${n(mix.code_only)} by code alone`],
["Judge's share of a task's reward", `median ${share(g.judge_share_median, 1)}`],
["Tiers", `critical ${n(t.critical)} (${share(t.critical, g.checks)}), important ${n(t.important)} (${share(t.important, g.checks)}), sanity ${n(t.sanity)} (${share(t.sanity, g.checks)}). A label only: tiers don't enter the formula`],
["Weights", `set in ${n(g.weighted_tasks)} tasks; the rest weight every check equally (a check without a weight counts 1)`],
...(g.mixed_weights ? [["Mixed weights", `${n(g.mixed_weights)} tasks give some checks weights that add up to about 1 and leave others without one, which count 1 each.
In ${n(g.mixed_heavy)} of them the unweighted checks decide 40% or more of the reward`]] : []),
["Facts come from", `the systems' databases in ${n(inp.db)} tasks, the workspace files in ${n(inp.workspace)}, both in ${n(inp.both)}`],
["The agent must", `change nothing in ${n(act.none)} tasks (its answer is graded), update databases in ${n(act.mutate_db)}, edit workspace files in ${n(act.edit_workspace)}, both in ${n(act.both)}`],
["What the judge reads", (g.judged_files || []).map(([f, c]) => `${esc(f)} for ${n(c)} checks`).join(", ") + ", as extracted text, up to 20,000 characters per file"],
["Gate and tamper checks", `supported by verify.py, used by ${n(g.uses_gates)} tasks`],
];
return `
By kind of task
Task
Tasks
Reward
Range
Graded by
Judge
Not scored when
In training
${rows}
General rubrics, across the dataset counted from every task's verifier_meta.json
${facts.map(([k, x]) => `
${esc(k)}
${x}
`).join("")}
Everywhere
A failure that isn't the model's (the testbed, the render, the judge) is not scored: the rollout has no reward, rather than 0. Xiaomi's harness masks these the same way.
Nothing that grades a task is in the sandbox while the agent works. Hidden tests, rubrics and the expected crash are uploaded after it finishes.
The judge prompts are sent in Chinese, unchanged, so scores stay comparable with Xiaomi's. Each task's Grading section has its prompt with an English translation.
Expected answers are never shown: not the rubric answers, not the code that checks them, not the Cyber proof of concept.
`;
else if (p.type === "archive") body.innerHTML = table([["Entry", "Size"], ...p.entries.map((x) => [x.name, bytes(x.size)])]);
else if (p.type === "error") body.innerHTML = emptyState("alert", "Couldn't preview this file", esc(p.error), `Download`);
else body.innerHTML = emptyState("file", "No preview for this file type", "", `Download`);
}
async function previewTable(ctx, sys, tbl, label) {
const body = openModal(`${label || sys} · ${tbl}`, sk.box(420, "border-radius:10px"), { kind: "table" });
try {
const d = await progress.wrap(api(dataUrl(ctx, "table", { system: sys, table: tbl, limit: 100 })));
if ($("#modal").hidden) return;
body.innerHTML = table([d.columns, ...d.rows.map((r) => r.map((c) => (c == null ? "" : String(c))))]) + `
${d.rows.length >= 100 ? "First 100 rows" : `${d.rows.length} row${d.rows.length === 1 ? "" : "s"}`} · ${d.columns.length} columns · the state of this database when the agent starts