AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Narration

skill-leventilo-mobius-narration · by leventilo

Produce time-aligned natural-language narration for a Mobius simulation, explaining the physics happening at each visible moment in clear prose. Generates the narration.segments[] sub-tree of SimSpec from physics, primitives, and execution telemetry. Run after primitive-generator and (typically) after simulation execution.

No reviews yet
0 installs
7 views
0.0% view→install

Install

$ agentstack add skill-leventilo-mobius-narration

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-leventilo-mobius-narration)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Narration? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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:

  1. The structured 3-paragraph card — three short bodies of prose (what, why, try_it) plus a claims_referenced[] bijection anchor and an optional caveats[] list. This is the editorial readout shown alongside the simulator. Static, paper-anchored, written once.
  2. The time-aligned segments[] array — same content as before (intro / equation / observation / insight / outro), gap-free coverage from t0 to tmax. 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].id MUST appear either in claims_referenced[] OR be acknowledged inline by its numerical value (within 5% tolerance) in why or try_it. No orphan claims.
  • Reverse direction. Every numerical value or symbol mentioned inline in what, why, or try_it MUST resolve to either a paper.numerical_claims[i] entry, a physics.constants[] entry (with source in {paper, standard}), or a viz.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:

  • what names a primitive id and what is on screen.
  • why cites at least one physics.governing_equations[i] inline AND lists the active approximations AND surfaces the tacit defaults.
  • try_it names at least one slider label AND at least one numerical_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 nmlambda; 1.5 mL; 1.96 mm → derived from lambda, L, d; 2.0 mmdelta_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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.