Install
$ agentstack add skill-leventilo-mobius-narration ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
narration
1. Purpose and scope
This skill writes the narration.segments[] array of the SimSpec. Each segment carries t_start, t_end, a markdown body, and a cue from intro | equation | observation | insight | outro. Segments cover simulation.time.t0 to tmax without gaps or overlaps, per Rule 10 of simspec-author.
Two consumers. On-screen captions during simulator playback render below the canvas in IBM Plex Sans, fading under the project's quintic smoothstep easing. The same array doubles as voiceover script (ElevenLabs synthesis for the demo video or a human read-through).
What this skill does NOT do. It does not interpret physics (physics-interpreter), does not invent equations (cites physics.governing_equations[]), does not produce audio (downstream), and does not editorialize.
2. Input
The populated SimSpec. Two modes.
Wet mode: telemetry available. The skill anchors segments to observed events (first reflection, Tc crossing, packet transmission), passed as simulation.events[].
Dry mode: telemetry unavailable. The skill walks the SimSpec (regime, equations, primitives, time bounds) and writes against predicted beats. Dry mode uses predictive tense ("we will see five fringes"); wet mode uses present indicative.
Optional orchestrator arguments: language (v1 English); per-segment word target (~30); segment-count cap (3-6 default, never above 8).
3. Output — the 3-paragraph contract (R5)
The skill writes the entire narration.* subtree of the SimSpec, conforming to simspec.schema.json#/$defs/narration. It has two co-equal outputs:
- The structured 3-paragraph card — three short bodies of prose (
what,why,try_it) plus aclaims_referenced[]bijection anchor and an optionalcaveats[]list. This is the editorial readout shown alongside the simulator. Static, paper-anchored, written once. - The time-aligned
segments[]array — same content as before (intro / equation / observation / insight / outro), gap-free coverage fromt0totmax. This is the voiceover/caption track that plays during simulation. Adjacent endpoints touch (segment[i].t_end == segment[i+1].t_start).
The two outputs are NOT redundant — the card is the editorial frame the user sees first, the segments are what they hear/read while the simulator runs. The card MUST be consistent with the segments (same equations, same approximations, same numerical anchors).
3.1 The three paragraphs
narration.what — 1–2 sentences, 30–400 chars, plain English. What is on screen. Names at least one primitives[].id (the user-facing label) or one paper.figures[].id. No inline LaTeX. No marketing. Answers: "what am I looking at?".
narration.why — 2–3 sentences, 60–800 chars, with inline KaTeX between $...$ for at least one physics.governing_equations[] entry. Why this picture. Enumerates the active approximations from physics.approximations[] (e.g. paraxial, scalar, monochromatic, Born-Oppenheimer, weakly-nonlinear) AND surfaces the tacit defaults (BK7 → n = 1.5, ℏ = m = 1, T/J = 2.3, etc.) that turn the equation into a number. Answers: "why does this picture come out the way it does?".
narration.try_it — 1–2 sentences, 40–400 chars. Which slider tests which claim. References at least one viz.sliders[].label AND at least one paper.numerical_claims[i] value (citing the paper figure or section). Answers: "what should I move, and what should I expect?".
3.2 Bidirectional binding (R5 critical)
narration.claims_referenced[] lists the claim ids (each paper.numerical_claims[i].id) that this narration cites. The bijection contract is enforced by science-integrity/scripts/check_narration_bijection.py:
- Forward direction. Every
paper.numerical_claims[i].idMUST appear either inclaims_referenced[]OR be acknowledged inline by its numerical value (within 5% tolerance) inwhyortry_it. No orphan claims. - Reverse direction. Every numerical value or symbol mentioned inline in
what,why, ortry_itMUST resolve to either apaper.numerical_claims[i]entry, aphysics.constants[]entry (withsourcein{paper, standard}), or aviz.sliders[].default. No ungrounded numbers.
Failure of either direction emits an integrity.checks[] entry with type: "narration-bijection", status: "fail" (forward-orphan) or status: "warn" (reverse-ungrounded).
3.3 Segments — same as v0.1, plus claims_referenced consistency
Each markdown body is plain prose with optional inline LaTeX between $...$. No headings, lists, tables. Backslashes JSON-escaped. KaTeX out of scope; the renderer treats $...$ as KaTeX when available else raw text.
The cue drives visual treatment: intro / outro fade with --c0; equation spawns an inline equation strip; observation / insight use body styling.
Numerical anchors used inside any segment.markdown are subject to the same reverse-bijection rule as the card: every value must resolve to paper.numerical_claims[], physics.constants[], or viz.sliders[].default. Phase 3 review (§5) runs the lexicon check on segments AND on the card.
3.4 Ineligible papers
When eligibility.tier == "ineligible" (see science-integrity/SKILL.md), this skill does not run. The orchestrator routes the paper to viz.scene_archetype = "explainer" (see viz-mapper/templates/explainer.html), which renders the eligibility report card directly from eligibility.reasons[]. No narration is written for these papers.
When eligibility.tier == "best_effort", this skill runs normally but MUST populate narration.caveats[] with one entry per failed gate AND each affected claim in claims_referenced[] MUST also appear in paper.numerical_claims[i].verification with status in {"partially-verified", "unverified"}. Whenever an unverified claim is cited inline, the literal phrase not independently verified MUST appear in the same paragraph (enforced by the bijection script).
3.5 Output format (mandatory)
You MUST emit your final output as a single fenced ``json block at the END of your reply, with NO prose after the closing fence. The orchestrator parses that block by regex (/``(?:json)?\s*\n([\s\S]*?)\n\s*`/) and ignores everything else in your text content.
The canonical artifact for narration (the 3-paragraph card of §3.1 plus the time-aligned segments of §3.3, plus a flattened narration_md rendition for the orchestrator's Phase F consumer):
{
"what": "1-2 sentences plain English: what is plotted, what the user sees.",
"why": "2-3 sentences citing the governing equation and the active approximations.",
"try_it": "1-2 sentences: which slider tests which claim, with the predicted value.",
"claims_referenced": ["lambda", "d", "delta_x"],
"caveats": [],
"segments": [
{ "t_start": 0.0, "t_end": 0.5, "cue": "intro", "markdown": "..." },
{ "t_start": 0.5, "t_end": 1.0, "cue": "outro", "markdown": "..." }
],
"narration_md": "# What\n\n...\n\n# Why\n\n...\n\n# Try it\n\n...\n"
}
Required keys: what, why, try_it (the 3-paragraph card per simspec.schema.json#/$defs/narration), claims_referenced[] (R5 bijection anchor), caveats[] (REQUIRED non-empty when eligibility.tier == "best_effort"; [] otherwise), segments[] (gap-free coverage from simulation.time.t0 to tmax), and narration_md (the same content rendered as a single Markdown string the orchestrator's Phase F consumer reads via pickArtifact).
segments[] must have cue ∈ {intro, equation, observation, insight, outro} and adjacent endpoints must touch (segment[i].t_end == segment[i+1].t_start).
If the SimSpec is eligibility.tier == "ineligible", this skill MUST NOT run. If the orchestrator dispatches it anyway, emit:
{ "error": "eligibility.tier == ineligible; refusing to narrate per SKILL.md §3.4" }
If the bijection check (Phase 3 review) fails on a fabricated noun, replace the body with the safe fallback:
{
"what": "(narration unavailable for this paper)",
"why": "(narration unavailable for this paper)",
"try_it": "(narration unavailable for this paper)",
"claims_referenced": [],
"caveats": ["narration_audit fail: fabricated noun detected in Phase 3 lexicon check"],
"segments": [{ "t_start": 0, "t_end": 1, "cue": "intro", "markdown": "(narration unavailable for this paper)" }],
"narration_md": "(narration unavailable for this paper)"
}
DO NOT emit prose, headings, or follow-up questions after the closing fence — they break the orchestrator's downstream consumption and get silently dropped.
4. Persona and voice rules
Audience: an undergraduate physics student or curious non-physicist. The voice must work for the judge panel (Boris, Cat, Thariq, Lydia, Ado, Jason; not all are physicists).
Tone: clear, slightly conversational, never condescending, never marketing. Active voice, short sentences. Concepts named by their proper terms once introduced. No exclamation marks, no emoji, no "amazingly" or "fascinatingly", no cheerleading.
Length: one to three sentences per segment, ~30 words target, 50 hard ceiling.
Honesty floor: every concept named must appear somewhere in the SimSpec - in physics.governing_equations[].variables, physics.constants[], physics.approximations[], primitives[].name, or a paper claim.
Marketing floor: segments do not pitch Mobius, do not mention the platform or AI. They describe the physics.
5. Segment-generation algorithm
Three phases.
Phase 1: outline. Walk the SimSpec and produce (t_start, t_end, cue, anchor) tuples from a closed beat set: a start beat at [t0, t0 + 0.05 dT] always tagged intro; an equation beat anchored to one physics.governing_equations[] entry; transition beats at regime changes (first reflection, transmission, phase change), read from simulation.events[] or predicted from time scales; one climax beat at the visually load-bearing moment; an outro at [tmax - 0.1 dT, tmax]. Drop lowest-priority transitions if more than 8; add a midpoint observation if fewer than 3.
Phase 2: content. For each beat, call Opus 4.7 (claude-opus-4-7, temperature 0.3, max_tokens 200) with the Section 7 system prompt plus the anchor and SimSpec subtree. equation cues include the anchored equation as inline $...$. observation cues describe what is visible using primitives[].name. insight cues explain why, anchored to the regime or approximations.
Phase 3: review. The full array is re-read in one Opus 4.7 call (temperature 0) for redundancy, contradictions, pacing, and gap-free coverage. The reviewer trims, reorders, or merges; it cannot introduce new claims. Honesty-floor violators are rewritten or dropped. Gaps extend the earlier segment's t_end.
6. Cue types
intro. First segment. Sets scene: what the user looks at, what equation governs, what regime. No motion yet.
equation. The moment an equation becomes visible or active. Always includes inline LaTeX from physics.governing_equations[].
observation. Describes a phenomenon visible on screen. Pure description. Anchored to a primitive name or event.
insight. Explains why. Causal. Anchored to regime, approximations, or a constant. Always follows an observation.
outro. Final segment. Connects to broader meaning, suggests a next experiment, or pins the result to a paper claim.
7. LLM prompt (verbatim)
The exact system prompt used in Phase 2:
> You are a science narrator for a physics simulation, in the register of Bartosz Ciechanowski's interactive essays: clear, calm, slightly conversational, never marketing. Audience: an undergraduate physics student or a curious non-physicist. You write in English. Short sentences, active verbs. Name concepts by their proper terms. > > You receive a JSON SimSpec describing the simulation, plus one beat anchor with a cue type and a time interval. Write the markdown body for exactly that beat. One to three sentences, target around thirty words, hard ceiling fifty. Plain prose only: no headings, no lists, no tables. Inline LaTeX between dollar signs is allowed when the cue is equation or when an explicit symbol from the SimSpec helps. > > Honesty floor: every physical concept you name must already appear in the SimSpec - in physics.governing_equations.variables, physics.constants, physics.approximations, primitives.name, or a paper claim. If a concept is not there, you cannot use it. > > Forbidden: exclamation marks, emoji, the words amazing, fascinating, beautiful, cool, incredible, simply, just. Forbidden: pitching the platform, mentioning AI or the simulator. Forbidden: speculation beyond what the SimSpec asserts. Forbidden: rhetorical questions. Forbidden: imperative direct address ("notice", "observe" - use passive or third-person). > > Output: only the markdown body. No JSON wrapper, no preamble, no postscript.
The user message carries the cue, the time interval, the relevant physics.governing_equations[i], the primitive ids active in the window, and the paper-claim subset for that window.
8. Worked examples — the 3-paragraph card pattern
Three narrations across canonical paper classes. Each one is a TEMPLATE: it shows how the contract is satisfied for the named regime, NOT a one-off lifted from runs/golden-*/. The actual runs/golden-*/narration.md predates this contract and will be re-emitted by narration v0.2 against this spec.
Every example below is structured the same way:
whatnames a primitive id and what is on screen.whycites at least onephysics.governing_equations[i]inline AND lists the active approximations AND surfaces the tacit defaults.try_itnames at least one slider label AND at least onenumerical_claims[i].id.claims_referenced[]enumerates the claim ids that appear in the inline text.
8.1 Wave-optics archetype — Young's double-slit
Source: runs/golden-young/{simspec,claims,figures}.json. Regime linear, equations $I(\\theta) = I_0 \\cos^2(\\pi d \\sin\\theta/\\lambda)\\,\\mathrm{sinc}^2(\\pi a \\sin\\theta/\\lambda)$ and $\\nabla^2 U + k^2 U = 0$. Approximations: paraxial, Fraunhofer, scalar-field, monochromatic. Tacit defaults: $\\lambda$ derived from SPDC degenerate pair.
{
"what": "A monochromatic plane wave passes through the double-slit-aperture and lands on the screen-intensity detector as a regular pattern of bright and dark vertical bands inside a slowly-decaying envelope (paper Fig. 3).",
"why": "Far-field intensity $I(\\theta) = I_0 \\cos^2(\\pi d \\sin\\theta / \\lambda)\\,\\mathrm{sinc}^2(\\pi a \\sin\\theta / \\lambda)$ holds in the paraxial, Fraunhofer, scalar, monochromatic regime. Tacit defaults: the source is a heralded SPDC photon at $\\lambda = 810\\,\\mathrm{nm}$, the screen sits at $L = 1.5\\,\\mathrm{m}$, the slits have width $a$ and separation $d$ measured by the paper's reticle.",
"try_it": "Drag d and watch the fringe spacing scale as $\\Delta y = \\lambda L / d$, predicted at 1.96 mm for the paper's parameters and measured at 2.0 mm in Fig. 3 (within 5 %). Push a wider and the sinc-squared envelope shrinks, killing the outer fringes.",
"claims_referenced": ["lambda", "d", "b", "L", "delta_x", "V"],
"caveats": []
}
Bijection check: every paper.numerical_claims[i].id (lambda, d, b, L, delta_x, V) appears in claims_referenced[]. Every inline number resolves: 810 nm → lambda; 1.5 m → L; 1.96 mm → derived from lambda, L, d; 2.0 mm → delta_x. The slider labels d, a resolve to viz.sliders[] entries. Forward and reverse pass.
8.2 Chaos-and-ODE archetype — Lorenz attractor
Source: runs/golden-lorenz/{simspec,claims,figures}.json. Regime strongly-nonlinear, equations $\\dot x = \\sigma(y - x)$, $\\dot y = x(\\rho - z) - y$, $\\dot z = xy - \\beta z$. Approximations: Galerkin-3-mode-truncation, Boussinesq. Tacit defaults: dimensionle
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: leventilo
- Source: leventilo/mobius
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.