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

Ml Content

skill-thtskaran-claude-skills-ml-content · by thtskaran

Generate publication-grade ML explainer videos and carousels the way 3Blue1Brown actually builds them — in real manimGL (NOT Manim Community Edition), as a tiny domain DSL of self-arranging Mobjects choreographed into transform-driven beats where every motion carries meaning. Overlap is prevented at construction time, not policed after render. Use for: 3b1b-style ML videos, paper-figure animation…

— No reviews yet
0 installs
40 views
0.0% view→install

Install

$ agentstack add skill-thtskaran-claude-skills-ml-content

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • • Prompt-injection patterns
  • • Secret / credential exfiltration
  • • Dangerous shell & filesystem operations
  • • Untrusted network calls
  • • Known-malicious package signatures
  • high Possible prompt-injection directive.

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ● Environment & secrets Used
  • ✓ 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 →

Reliability & compatibility

— Not yet reviewed
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 Ml Content? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

ml-content

Generate ML explainer content that looks like 3Blue1Brown, not like AI slop.

> ## ⚠️ PRIME DIRECTIVE — treat every video as nuclear. ZERO errors ship. > The content goes public to an audience that will fact-check it. A single wrong number, mislabeled quantity, or overstated claim destroys trust in everything else and gets screenshotted. So: nothing — not even slightly — may be wrong. Every spoken line, every on-screen number and label, every caption, and the thumbnail must be verified against the primary source before it is rendered, and audited again on the rendered video before it ships (§10, the non-negotiable gate). If you cannot cite the exact source line for a claim, you do not say it, write it, or put it on screen. Soften it or cut it. No "dramatic license" on numbers. When in doubt, it is wrong until proven right.

This skill was rebuilt from a full read of Grant Sanderson's actual production code (github.com/3b1b/videos, 503K LOC, 2015→2026) and the real manimGL engine (github.com/3b1b/manim). Every rule below is grounded in that source with file:line citations. Where this skill once guessed, it now measures.

The video engine is manimGL (the 3b1b version), driven by manimgl. Manim Community Edition (CE) is a different library with a different, incompatible API — code written for one crashes on the other. Static IG carousels use HTML/matplotlib (see the Carousel section); everything animated is manimGL.


0. Why the old output was slop (read this once)

The previous version of this skill produced overlapping elements, weak animation, no consistency, and infographic-feeling stills. The root causes, now fixed:

  1. It shipped Manim CE code while preaching manimGL. The old template used from manim import *, MathTex, ThreeDScene, set_camera_orientation, set_fill_by_value, Create, begin_ambient_camera_rotation — none of which exist in manimGL (grep over the entire engine = 0 hits). It would not even run.
  2. 100% of real output was actually built in matplotlib, hand-placing ~57 text calls + ~20 boxes per scene with absolute coordinates. That is the worst possible tool for animation: no relative layout, no transform system, no camera. Overlap is guaranteed.
  3. It treated overlap as a validation problem (bbox asserts, frame validators, ffmpeg caption-pads) instead of a construction problem. 3b1b never validates overlap — it makes overlap structurally impossible by building self-arranging objects.
  4. It treated motion as decoration. Elements faded in from nowhere as disconnected islands. In real 3b1b, objects are born from the thing they abstract (TransformFromCopy), so every motion teaches.
  5. Planning was marketing copy with word-count targets. Real 3b1b planning is an ordered list of named teaching beats that reads top-to-bottom as the narration.

The fix is a different mental model, encoded as the Six Laws below.


1. The engine reality (the single most important section)

Start every manim file with:

from manim_imports_ext import *      # in the 3b1b/videos repo
# or, standalone:  from manimlib import *

Scene base class is InteractiveScene (interactive_scene.py:66) — for 2D and 3D. In the entire 2025 corpus: InteractiveScene subclassed 385 times, ThreeDScene (scene.py:930) 0 times — it exists but 3b1b never uses it. 3D is achieved on an InteractiveScene by moving self.frame.

The camera is self.frame (a CameraFrame mobject; scene.py:112). Local alias frame = self.frame appears 114× in 2025 code. self.camera.frame appears 0×.

Render / iterate:

manimgl file.py SceneName              # render
manimgl file.py SceneName -se 120      # drop into IPython at line 120 (the dev loop)
manimgl file.py SceneName -w           # write to file
# inside the embed: checkpoint_paste() runs clipboard code with checkpoint rewind

CE landmines — Table A: these genuinely CRASH on manimGL (absent symbols)

| You must NOT emit (CE) | Use instead (manimGL) | Evidence | |---|---|---| | from manim import * | from manim_imports_ext import * / from manimlib import * | CLAUDE.md:59 (loads the wrong library) | | MathTex(...) | Tex(...) | grep MathTex over repo = 0; CLAUDE.md:82 | | self.set_camera_orientation(phi=,theta=,zoom=) | self.frame.reorient(theta, phi, gamma, center, height) | absent; camera_frame.py:172 | | self.move_camera(...) | self.play(self.frame.animate.reorient(...)) | absent | | self.begin_ambient_camera_rotation() | self.frame.add_ambient_rotation(1 * DEG) | absent; camera_frame.py:212 | | self.add_fixed_in_frame_mobjects(m) | m.fix_in_frame() | absent (no scene-level helper) | | Create(m) | ShowCreation(m) | absent; creation.py:48 | | Unwrite(m) | Uncreate(m) or FadeOut(m) | absent | | surface.set_fill_by_value(...) | surface.set_color(c, opacity) / set_color_by_xyz_func(...) | absent | | Circumscribe(m) | FlashAround(m) | absent in engine (the one truly-missing indicator) | | Wiggle(m) (class) | WiggleOutThenIn(m) or rate_func=wiggle | no Wiggle class; indication.py:355 | | ease_in_out_*, smoothstep, easeOutCubic | the 15 real rate funcs (§7) | not in rate_functions.py | | FadeInFrom, FadeOutAndShift, SpinInFromNothing, AddTextLetterByLetter | FadeIn(m, shift=, scale=), AddTextWordByWord | absent |

Self-check (must return nothing):

grep -nE 'MathTex|from manim import \*|set_camera_orientation|begin_ambient_camera_rotation|move_camera|add_fixed_in_frame_mobjects|set_fill_by_value|\bCreate\(|\bCircumscribe\b|\bWiggle\(|\bUnwrite\(|FadeInFrom|SpinInFromNothing|AddTextLetterByLetter' your_file.py

Table B: these RUN on manimGL but are wrong/stale style — don't emit anyway

| Avoid (valid but off-style) | Prefer | Why | |---|---|---| | class S(Scene) / class S(ThreeDScene) | class S(InteractiveScene) | both exist & run, but 2025 corpus is 385× InteractiveScene, 0× ThreeDScene; 3D uses self.frame | | TransformMatchingTex(a, b) | TransformMatchingStrings(a, b) | TransformMatchingTex exists (subclasses Strings) but 3b1b uses Strings | | eq.set_color_by_tex(tok, c) | Tex(R"...", t2c={tok: c}) or inline eq[tok].set_color(c) | set_color_by_tex is a real Tex method (tex_mobject.py:207) but ~unused; inline set_color dominates | | Indicate(m) / CircleIndicate(m) | FlashAround(m) / Flash / FlashUnder | both exist & are used (indication.py:73,142); modern corpus just reaches for Flash* far more | | DEGREES | DEG | DEGREES is a live alias of DEG (won't crash); 2025 uses DEG ~520× vs DEGREES 3× |

> The split matters: don't let a self-check grep reject valid 3b1b code. Indicate, TransformMatchingTex, set_color_by_tex, and DEGREES are style calls, not crashers — only Table A NameErrors.


2. The mental model (what makes it 3b1b, not slop)

> You are not laying out a frame. You are building a small cast of self-arranging objects and transforming them through a sequence of beats where each motion is the explanation.

Two layers, every video:

  1. helpers.py — the domain DSL. 4–10 Mobject subclasses that build and arrange themselves (WeightMatrix, NumericEmbedding, EmbeddingArray, Dial, NeuralNetwork, ContextAnimation — _2024/transformers/helpers.py), plus 2–5 show_*(scene, ...) choreography verbs. Define the cast before writing a single construct().
  2. Scene files — thin InteractiveScene subclasses whose construct() is a flat list of # Beat name comments, each assembling DSL objects and animating them.

Everything below serves this model.


3. The Six Laws (non-negotiable)

Law 1 — Relative layout only. Overlap is structural, not policed.

Real 3b1b layout is overwhelmingly relative: next_to used ~10,500×, arrange ~2,460× across the repo, versus only a handful of absolute move_to([x,y,z]) content placements — and nearly all of those absolutes are the camera, never content. Forbid absolute content coordinates. move_to([x,y,z]) and frame.animate.move_to(...) are for the camera/light source only.

Allowed positioning primitives — content position ALWAYS comes from one of these:

a.next_to(b, DOWN, buff=MED_LARGE_BUFF)     # relative to another object
a.next_to(b, RIGHT).match_y(c)              # compound: x from b, y aligned to c
a.align_to(b, LEFT)                         # share an edge
a.to_edge(UP, buff=LARGE_BUFF)              # to a frame edge (chrome only)
a.to_corner(UL)                             # to a corner (chrome only)
VGroup(*items).arrange(DOWN, buff=MED_SMALL_BUFF, aligned_edge=LEFT)
VGroup(*items).arrange_in_grid(rows, cols, buff=...)
dots = Dot().get_grid(n_rows, n_cols, buff_ratio=0.5)
a.match_x(b) / a.match_y(b) / a.match_width(b) / a.match_height(b)

Boxes and pills are content-sized, never hand-sized:

rect = SurroundingRectangle(label, buff=SMALL_BUFF)   # measures real glyph bounds
brace = Brace(group, DOWN); brace.get_text("12,288")   # fits the span, anchors at tip
under = Underline(word)                                 # width derived from content

SurroundingRectangle is used ~1,400× in the repo and cannot clip or mis-center because its size = target.get_shape() + 2*buff (shape_matchers.py:22). Delete every text_width() character-count heuristic — it has no analog in real code.

The buff ladder is the ONLY source of gaps (default_config.yml:109):

SMALL_BUFF=0.1   MED_SMALL_BUFF=0.25   MED_LARGE_BUFF=0.5   LARGE_BUFF=1.0

Never write buff=0.37. Consistent margins across scenes come for free from this ladder.

Build the group, then place the group. Assemble a VGroup declaratively, .arrange() its internals once, then position the whole thing. This is the dominant pattern across the repo's thousands of VGroups. Never position leaf elements against screen coordinates.

Labels on moving targets follow live: label.always.next_to(target, UP, buff=SMALL_BUFF) (or add_updater). A single-frame bbox assert can't catch a mid-animation collision; a re-running next_to never collides.

> Because of Law 1, the old "Five-Layer Defense" (bbox asserts + frame validator + ffmpeg caption pad) is retired. Overlap is prevented at construction. Keep at most a light visual probe-frame check for taste and timing, not collision.

Law 2 — Motion carries meaning. Born-from, never spawn.

The highest-frequency, highest-leverage technique in the corpus: a key object enters by transforming from the concrete thing it abstracts, so the causal link is visible.

# the DALL·E image literally dissolves INTO the numeric vector entries (attention.py:128)
self.play(LaggedStart(*(bake_mobject_into_vector_entries(img, vec) for img, vec in ...)))
# 12,288 numbers collapse into a single symbol E_n  (attention.py:200)
self.play(FadeTransform(entry, sym))
# a formula term is delivered by morphing the data column it denotes (ml_basics.py:642)
self.play(TransformFromCopy(data_column, x_symbols))

Transform-family hard counts (attention.py): TransformFromCopy 46 > FadeTransform 31 > ReplacementTransform 6 > plain Transform ~5 ; TransformMatchingTex 0.

  • TransformFromCopy(src, dst) is THE workhorse — source persists, a copy morphs to the destination, so the viewer sees "this becomes that" while "this" is still there.
  • For equation retitles, TransformMatchingStrings (not ...Tex).
  • Never FadeIn a load-bearing object from nothing. Decorative scaffolding can fade in; the thing the lesson is about must be born from its referent.

time_span=(start, end) choreographs a reveal inside ONE self.play (used 638× in the repo) so a camera move and a multi-part reveal cascade together instead of firing simultaneously:

# the softmax aha — one play, run_time=3, cascaded (attention.py:921)
self.play(
    self.frame.animate.reorient(...),
    GrowArrow(arrow, time_span=(1, 2)),
    FadeIn(label, time_span=(1, 2)),
    TransformFromCopy(ndp_col, softmax_col, time_span=(1.5, 3)),
    run_time=3,
)

generate_target / MoveToTarget (127 refs) is how dozens of objects snap into a new arrangement with zero hand-keyed coordinates:

grp.target = grp.generate_target()
grp.target.arrange(RIGHT, buff=0.15)   # mutate the target with normal layout calls
grp.target.scale(0.65).next_to(anchor, DOWN)
self.play(MoveToTarget(grp))

Law 3 — A tiny domain DSL gives consistency.

Every video defines ~6 custom Mobjects/Animations in a helpers.py plus ~6 scene-local get_*/show_* factories. A vocabulary, not a framework. This is the mechanism for both consistency and no-overlap (the objects arrange themselves).

> For ML content, don't start from scratch — vendor 3b1b's transformer DSL (NumericEmbedding, WeightMatrix, EmbeddingArray, ContextAnimation, value_to_color). It is the single fastest path to the authentic "columns of real numbers" look. See §13.

Mobject-subclass recipe (model: Dial, helpers.py:655; MachineWithDials:761):

class WidgetThing(VGroup):
    def __init__(self, value=0, ...):
        # 1. build sub-parts
        body = Rectangle(...); needle = Line(...)
        # 2. lay them out RELATIVELY (ratio buffers, never move_to([x,y,0]))
        ticks = Line(...).get_grid(1, n, buff_ratio=0.5)
        ticks.set_width(body.get_width() - SMALL_BUFF); ticks.move_to(body)
        # 3. assemble, 4. name every meaningful part
        super().__init__(body, ticks, needle)
        self.body, self.needle = body, needle
        # 5. a state mutator that recomputes geometry+style from the logical value
        self.set_value(value)
    def set_value(self, v):
        self.needle.put_start_and_end_on(self.get_center(), self._value_to_point(v))
        self.needle.set_color(value_to_color(v))     # color is a pure function of value
    def animate_set_value(self, v, **kw):            # 6. methods that RETURN animations
        return AnimationGroup(self.animate.set_value(v), ...)

show_*(scene, ...) choreography-verb recipe (model: show_matrix_vector_product, helpers.py:97):

  • first arg is scene; the function calls scene.play/scene.wait internally,
  • owns its transient highlights via a last_rects/to_fade accumulator so exactly one highlight is ever on screen,
  • returns the persistent mobjects it created.

Variants are 2-line subclasses of a shared base overriding one class attribute — never a copy-pasted construct() (model HighlightEarthOrbit(NearestPlanets) with highlighted_orbit = 2, and its sibling HighlightMarsOrbit(NearestPlanets) with = 3; planets.py:2202). Domain numbers live in ALL-CAPS module constants with one conversion_factor (planets.py:4), so sizes can't disagree across scenes.

Large data uses honest ellipsis: render a finite set, swap one element for Tex(R"\dots") (or ellipses_row=-2, default in WeightMatrix), so big arrays read as big without overflowing (helpers.py:478, 620).

Law 4 — Earned 3D only, and 3D is never static.

3D is earned only when a quantity's dimensionality is the payload (a vector space, a surface f(x,y)=z, a volumetric field). Roughly half of even the attention video is intentionally flat. A flat data series in 3D is the #1 faux-3D tell — and the old matplotlib "isometric stack of parallelograms" cube is exactly the slop to never produce.

> **Production lesson (§13): 3D actively HURTS a stack of thin layers (interleaved attention layers, a residual tower) — viewed at an angle they collapse into one solid block and the per-layer colors vanish. Render those flat, face-on**. Reserve 3D for clouds, surfaces, and collapsing volumes.

> **Counter-lesson — over-flattening is its own slop (learned shipping the DeepSeek/Qwen attention seri

…

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.