About psychopomp
psychopomp is a skill for creating narrated explainer reels from verified software changes. It guides authors through research, scripting, scene construction, review, and rendering, with visuals timed to narration.
Key features
- 01Fact-check changes against diffs and tests
- 02Write clip-based narration scripts
- 03Compose animated scenes with reusable actors
- 04Align visual beats to transcript phrases
- 05Review and render complete reels
Use cases
- 01Explain a pull request through animated scenes
- 02Create narrated before-and-after code walkthroughs
- 03Show behavior changes with timed callouts and counters
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ kitlangton/ psychopomp --skill psychopompAfter installing, type/psychopompin Claude Code to confirm it loaded.
Bundled filesSTORY.md
Details
From the project READMEPsychopomp explainer reels
An explainer reel is several independently authored Scene Plans joined on one clock: behavior stories on the GPU Stage (orbs, cards, beams, packets, camera, bloom), pinned Callouts, Rolling Numbers, changes as animated Stepped Diffs, captions in a terminal voice, and optional spoken narration. When narrated, every visual beat waits for a phrase in the transcript, so re-voicing re-times the film automatically.
The working examples are in scenes/pr-walkthrough: src/flagship.rs (#50825 as a
Stage film that zooms into its code), src/stop_stage.rs (#50042 combining Stage,
Callouts, Rolling Number, and a Zoom into an annotated code diff), and src/lib.rs
(the five-PR reel with sequence diagrams). Copy their shape; do not start from a
blank crate. Component payloads, channels, and commands live in SCENE_PLANS.md
("Make A Narrated Explainer Reel") and terms in CONTEXT.md; read both before
authoring. Engineering rules are in AGENTS.md.
Steps
-
Establish the facts. For each change, read the actual diff and PR description (
gh pr diff,gh pr view) and write one sentence each for the broken behavior, the fixed behavior, and the change. Done when every claim you will narrate is traceable to code, a test, or a reproduced run; mark anything you could not verify and keep it out of the script. -
Write the script as
scenes/<name>/narration/script.json, one clip per part: intro, then per change<slug>-before,<slug>-after,<slug>-code, then outro. Follow STORY.md when writing it. Done when each clip is under about 30 seconds spoken and every planned visual beat has a distinct anchor phrase in it. -
Voice it (optional). Draft first with no credentials (
--draftuses macOSsay), or synthesize final narration with ElevenLabs (engine: "elevenlabs", default modeleleven_v4) or Fish Audio (engine: "fish"):{ "engine": "elevenlabs", "model": "eleven_v4", "voice": "<elevenlabs-voice-id>", "settings": { "stability": 0.5, "similarity": 0.7 }, "clips": [ { "id": "change-before", "text": "[warm, conversational voice] ..." } ] }# Draft (no API key required) bun scripts/narrate.ts scenes/<name>/narration/script.json --draft # ElevenLabs (requires ELEVENLABS_API_KEY and script.voice) ELEVENLABS_API_KEY=... bun scripts/narrate.ts scenes/<name>/narration/script.json # Fish Audio (requires FISH_AUDIO_API_KEY) FISH_AUDIO_API_KEY=... bun scripts/narrate.ts scenes/<name>/narration/script.jsonscripts/narrate.tsnormalizes loudness, transcribes word timings with Whisper, and writes<id>.mp3,<id>.words.json, andnarration.json. When switching voices or engines, rebuild the Scene Plan against the new word timings.Or declare the narration in the Scene Program with
psychopomp-media(SCENE_PLANS.md, "Declare Narration And Sound"):media.say(id, &voice, text),media.dialogue,media.sfx, andaudio.derive(Effect::pitch(..)), thenmedia.finish()?. Each line is generated once and recorded inmedia.lock.json; later runs call nothing unless a declaration changed, and ElevenLabs words come from its character alignment with the script's spelling. Check the cost first withPSYCHOPOMP_MEDIA=plan cargo run -p <crate>(zero API calls), time withPSYCHOPOMP_MEDIA=draft, and clean up withPSYCHOPOMP_MEDIA=prune. Move a narrate.ts scene withcargo run -p psychopomp-media -- adopt scenes/<name>/narration(no regeneration). Done when a plain run reports every resource=. -
Author the Scene Program in
scenes/<name>(the workspace picks it up; add it toverify.jsonwith a few key times). Usepsychopomp::score(SCENE_PLANS.md, "Author With The Score DSL") soPlanBuilderis the onlymutbinding and every actor (Stage,Camera,Caption,Callout,RollingNumber,Checklist,Meter,Bars,Subtitles,Confetti,Terminal,Chat,ChangedFiles,LowerThird,Lens) is an immutable value whose methods return composableBeats (.then,.also,.with,.on_end,.after,.early,stagger,each,at!(scene, time => ...)). Schedule clips withNarration::reading, build elements withStageElement::card|orb|beam|packet|label|ringand relativepsychopomp::layout::Placementenvelopes onStagePost::RESTRAINED, frame withCaption::header|chip|footer, and play sounds withsfx::*.beat(id, gain_db)(oraudio.beat(gain_db)forpsychopomp-media). Load theexplainer-motionskill and apply it to every beat: cardssettle_in, beamsconnect, messagessend, replies wait for.reply(), impactshit, the heroorb_in, the fix'srewind; useCalloutto pin annotations to Stage elements or Editor code ranges andRollingNumberfor live counters/timers; drive camera moves throughstage.camera()(establish,frame,follow,release,orbit,whip,dolly_zoom). Reach for the visualization and text overlays instead of hand-building them from Stage labels and rings:Checklistfor checks that run and resolve,Meter(MeterPlan::countdown) for a timeout ring,Barsfor before/after numbers,Confettifor a success beat, andSubtitles(SubtitlesPlan::from_spoken) to burn in word-timed captions. Changes areDiffs ofkeep,add(step), andremove(step)lines, each step keyed to a phrase. Done whencargo run -p <crate>writes the reel andcargo run --release -- plan validate <reel>reports valid. A missing phrase panics with its clip: change the anchor to words the transcript actually contains. -
Review before rendering. Get segment spans from
plan inspect <reel>, then contact-sheet each segment at its before, switch, after, and code beats:bun scripts/sheet.ts <reel> t1,t2,... --theme neutral --shutter --out output/sheet.jpgWhen tuning the look, set
PSYCHOPOMP_SHADER_DIR=crates/psychopomp-render/src/renderand editstage.wgsl/stage_post.wgsl: every frame and sheet picks up shader edits without a Rust rebuild. Then render one behavior segment with audio (plan render <reel> out.mp4 --cue <scene-id>) or a 2–3 second window (--range a..b) and inspect frames extracted during motion. Done when every segment has been looked at and no text overlaps, clips, or reads against the wrong chip. -
Render and verify the whole reel once, after the sheets and short windows pass. Run it in the foreground with a long timeout and never end a turn while it is still running:
cargo run --release -- plan render <reel> output/<name>.mp4 --theme neutralDone when
ffprobeshows 1920x1080, 60 fps, AAC audio, and a duration matchingplan inspect, and loudness is near -16 LUFS with peaks under -1 dBFS. -
Deliver the MP4 path. Commit the scene, narration assets, and any engine changes in Psychopomp with conventional messages.
Improving Psychopomp
When a reel needs something the engine cannot do, add it to Psychopomp rather than
working around it in one scene: plan types and validation in crates/psychopomp,
strict-channel preflight in plan_runtime, pixels in render, GPU-free tests plus
an #[ignore] GPU test, and the docs AGENTS.md asks you to keep current. Run
cargo run --release -- verify baseline before the change and verify compare --expect <scenes you changed> after it to prove every other showroom and film is
untouched.
Build from small reusable pieces. Interpolation, easing, curves, shape ports, and
connectors belong in psychopomp::math (organized like pmndrs math: core lerp/
remap/smoothstep, easing, curve, shapes, random; glam vectors). Extend it
instead of inlining math in a renderer or scene, and split renderers into one small
helper per element, as render/stage.rs does. Keep
cargo fmt --check, cargo clippy --workspace --all-targets --all-features -- -D warnings,
and cargo test --workspace green.