Quest Walkthrough Video Framework¶
The VIDEO lane of the quest-perfection loop: turn a verified quest run into a side-by-side video β the rendered quest page on the left, the recorded sandbox terminal session on the right β publish it to YouTube, and reference it from the quest's own page. One artifact serves two goals: video evidence that the quest actually works as written, and educational content a learner can watch before (or instead of) reading.
βββββββββββββββββββββββ quest-video.yml (dispatch) βββββββββββββββββββββββ
β β
walkthrough_plan.py βββΊ walk-plan.json βββΊ agentic execute engine βββΊ walk-evidence.json β
(deterministic planner) (agentic_validate.py, (sealed transcript: β
β² workflow-minted β the commands + status + β
β or: source_run_id reuses the SAME engine + contract detail per quest) β
β sealed artifact of a as quest-perfection.yml) β β
β quest-perfection run βΌ β
β walkthrough_video.mjs (Playwright) β
β ββββββββββββββββ¬ββββββββββββββββββββ β
β β quest page β terminal replay β β
β β (live site, β (typed commands + β β
β β auto-scroll) β β/β/β
/~ outcomes) β β
β ββββββββββββββββ΄ββββββββββββββββββββ β
β videos/<level>-<slug>.mp4 β
β videos/manifest.json (chapters) β
β β β
β video_manifest.py build βββΊ upload-plan.json β
β (title/description/chapters/tags β deterministic) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β publish: true + YOUTUBE_* secrets
βΌ
youtube_upload.py βββΊ YouTube (unlisted) βββΊ uploads.json
β
video_manifest.py apply βββΊ quest frontmatter walkthrough_video:
+ .quests/videos.yml (catalog)
β
ONE `quest-video`-labeled PR βββΊ human review βββΊ merge
β
_includes/quest/quest-video.html embeds it on the quest page
Why this design¶
The framework deliberately reuses the quest-perfection loop's architecture instead of inventing a recording rig:
- The evidence already exists. The perfection loop's execute engine (
test/quest-validator/agentic_validate.py --mode execute) already plays each quest's commands for real in a disposable runner sandbox and records a per-quest transcript (verdict_obj.commands[]: command, status, detail). A video of "a run of the quest" is a rendering problem, not a new measurement problem. - Workflow-minted, sealed evidence. Exactly like
quest-perfection.yml, the engine runs as a deterministic workflow step (its job env carries the Claude auth β Claude Code scrubs auth env vars from Bash-tool subprocesses, so the engine can never run inside an agent). Unlike the walk lane, no agent step runs in this workflow at all, so there is nothing to seal against: every byte between the engine and YouTube is produced by deterministic, reviewable scripts. - Replay, don't screen-grab. The recorder (
scripts/quest/walkthrough_video.mjs) renders a studio page β left pane: a capture of the real quest page off the live site, auto-scrolling in sync; right pane: the transcript typed out with its β passed / β failed / β skipped / ~ reasoned outcomes β and records it with Playwright's built-in video recording. That makes the video deterministic given (plan, evidence), honest (it renders only witnessed commands and outcomes, never a model's synthesis), and cheap (a re-render costs $0 of model spend, especially withsource_run_idevidence reuse). - Human holds the publish button. Uploads default to unlisted, and the frontmatter/catalog change ships as a
quest-video-labeled PR that matches no auto-merge policy incontent-auto-merge.yml. Reviewing the PR is reviewing the video; the human merges the reference and flips the video public on YouTube when satisfied.
Components¶
| Piece | Role |
|---|---|
.github/workflows/quest-video.yml |
The lane: gate β plan β evidence (fresh engine pass, or source_run_id reuse of a quest-perfection/quest-walkthrough artifact) β record β optional publish leg. Dispatch-only, OFF by default. |
scripts/quest/walkthrough_video.mjs |
Deterministic side-by-side renderer: studio HTML + timed replay + Playwright recordVideo β videos/<level>-<slug>.webm (+ .mp4 when ffmpeg is present) + manifest.json with per-step chapter times + <name>.findings.json capturing every step that did not cleanly pass (see "Closing the loop"). |
scripts/quest/video_manifest.py |
build: recorder manifest β YouTube upload plan (title β€100 chars, description with chapters + provenance links, bounded tags, category Education, privacy). apply: uploads β the quest's walkthrough_video: frontmatter block (+ lastmod bump) and the committed catalog. Unit-tested (scripts/quest/test_video_manifest.py). |
scripts/quest/youtube_upload.py |
Stdlib-only YouTube Data API v3 resumable uploader (OAuth refresh-token flow, chunked with resume/retry, optional playlist). Idempotent against the catalog β a quest with a published video is skipped unless --force. |
.quests/videos.yml |
The committed catalog: quest permalink β published video (id/url/recorded/run_url/slice/verdict/score). The queryable index; the per-quest frontmatter block is what the site renders. |
_includes/quest/quest-video.html + _layouts/quest.html + assets/css/quest-system.css |
The embed: a lazy, privacy-enhanced (youtube-nocookie.com) responsive player above the quest body, rendered only when the page's frontmatter carries walkthrough_video. |
quest_registry.py |
Schema registration: walkthrough_video is an optional structured field (WALKTHROUGH_VIDEO_KEYS = provider/id/url/recorded/run_url). |
The frontmatter contract¶
Written only by video_manifest.py apply (idempotent text edit β every other frontmatter byte is left alone); rendered by the quest layout; indexed by the catalog:
walkthrough_video:
provider: youtube
id: dQw4w9WgXcQ
url: https://www.youtube.com/watch?v=dQw4w9WgXcQ
recorded: '2026-08-19'
run_url: https://github.com/bamr87/it-journey/actions/runs/<run id>
run_url is the provenance link β the CI run whose sealed evidence the video replays. The normalizer never strips the block (it only drops RETIRED_FIELDS), and _data/quests/* generation does not consume it, so make quest-data is not required after an apply.
Which quest gets recorded¶
βA side-by-side run of the main questβ: by default the recorder picks the first main_quest in plan order that has recorded evidence β the plan is dependency-ordered, so that is the slice's main quest. Options widen or pin the set:
--quest <permalink|path|slug>β pin one quest explicitly (the workflow'squestinput).--all-mainβ every evidenced main quest in the slice (theall_maininput).--allβ every evidenced quest (side quests too); CLI-only, for local batches.
Quests without recorded evidence are skipped (listed in the manifest's failures), because a video without evidence would be staging, not proof. --allow-no-evidence exists for local demos only.
Gates and setup (OFF by default)¶
Two staged gates, mirroring the fleet's kill-switch discipline:
- Record β requires the
QUEST_VIDEO_ENABLEDrepo variable AND Claude auth (CLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_API_KEY) β or asource_run_id, which needs no model at all. Output: the video as a 14-day run artifact. Nothing published, nothing committed. - Publish β additionally requires the
publish: truedispatch input AND the YouTube secrets. Uploads land unlisted; the reference PR awaits human review.
# 1) engine auth (skip if the perfection loop is already set up)
gh secret set CLAUDE_CODE_OAUTH_TOKEN --repo bamr87/it-journey
# 2) YouTube credentials (see below)
gh secret set YOUTUBE_CLIENT_ID --repo bamr87/it-journey
gh secret set YOUTUBE_CLIENT_SECRET --repo bamr87/it-journey
gh secret set YOUTUBE_REFRESH_TOKEN --repo bamr87/it-journey
gh secret set YOUTUBE_PLAYLIST_ID --repo bamr87/it-journey # optional
# 3) the kill switch
gh variable set QUEST_VIDEO_ENABLED --body true --repo bamr87/it-journey
Getting the YouTube credentials (one-time, ~10 minutes)¶
- In Google Cloud Console, create (or reuse) a project β Enable the YouTube Data API v3.
- OAuth consent screen: External, add yourself as a test user (Testing status is fine β refresh tokens for test users of a "Desktop" client don't expire on the 7-day schedule web clients do, but note Google can still revoke; re-mint if uploads start failing with
invalid_grant). - Credentials β Create credentials β OAuth client ID β Desktop app. Note the client id + secret.
- Mint a refresh token with the upload scope (add
https://www.googleapis.com/auth/youtubeinstead if you want playlist filing too):
# any machine with a browser; uses the OAuth out-of-band-free loopback flow
python3 -c "
import urllib.parse
p = dict(client_id='YOUR_CLIENT_ID', redirect_uri='http://localhost:8080',
response_type='code', access_type='offline', prompt='consent',
scope='https://www.googleapis.com/auth/youtube.upload')
print('open: https://accounts.google.com/o/oauth2/v2/auth?' + urllib.parse.urlencode(p))"
# open the URL, approve, copy the ?code=... from the redirected localhost URL, then:
curl -s https://oauth2.googleapis.com/token \
-d client_id=YOUR_CLIENT_ID -d client_secret=YOUR_CLIENT_SECRET \
-d code=THE_CODE -d grant_type=authorization_code \
-d redirect_uri=http://localhost:8080
# β the JSON's "refresh_token" is what goes into YOUTUBE_REFRESH_TOKEN
- Verify from anywhere:
YOUTUBE_CLIENT_ID=β¦ YOUTUBE_CLIENT_SECRET=β¦ YOUTUBE_REFRESH_TOKEN=β¦ python3 scripts/quest/youtube_upload.py --verify-only.
Quota note: a video upload costs ~1600 units of the default 10,000/day YouTube API quota β ~6 uploads/day headroom, far above this lane's cadence.
Running it¶
CI (the normal path): Actions β π¬ Quest Walkthrough Video β Run workflow. Typical dispatches:
- Fresh recording of a slice's main quest: set
character+level(blank = date-rotated), leavepublishoff β review thequest-video-<slug>artifact. - Free re-render from yesterday's perfection run: set
source_run_idto that run's id (+ the samecharacter/level) β no model spend. - Publish: same dispatch with
publish: trueβ unlisted upload + the reference PR.
Locally (render only; uses whatever walk-plan.json/walk-evidence.json a walkthrough left in the working dir):
make quest-video-plan # preview which quest would be recorded (no browser, no cost)
make quest-video # render videos/ from walk-plan.json + walk-evidence.json
make quest-video-selftest # unit tests for the deterministic manifest/apply arm
The renderer needs npm install --no-save playwright && npx playwright install chromium ffmpeg; an ffmpeg on PATH additionally produces the .mp4 (YouTube accepts the raw .webm either way).
Closing the loop: captured results β review β fix¶
A recording is not just evidence that the quest ran β it is a measurement, and steps that did not cleanly pass must flow back into the walkthrough framework for repair rather than dying inside an mp4. The lane captures its results in four aligned places, all derived from the same analyzeResults() pass over the full evidence transcript (never the render-capped subset, so a --max-commands cap can never hide a broken step):
- In the video itself β the verdict outro shows the per-status breakdown (
N passed Β· N failed Β· N skipped Β· N reasoned) and lists up to four steps that did not cleanly pass, with their recorded details, so a reviewer watching the video sees the problems without opening anything else. <out>/<name>.findings.json(rides the run artifact) β the machine-readable review handoff: quest identity, per-status counts, and every failed/skipped command with its detail, plus the fix-lane recipe below.- The publish PR β a "Witnessed results" section with the aggregate breakdown and a per-step issues table, so "review = watch the video" includes "here is exactly what didn't work".
- The catalog β each
.quests/videos.ymlentry carriesissues: N; a non-zero count on a published video is a standing flag for dashboards and a re-record trigger once the quest is fixed.
Feeding the fix lane needs no new machinery: quest-fix.yml already accepts arbitrary artifact names, and this lane's run artifact contains the sealed walk-evidence.json the recording replayed. To repair what a video witnessed, dispatch quest-fix with:
plan_artifact = quest-video-<character>-<level>
evidence_artifact = quest-video-<character>-<level>
source_run_id = <the quest-video run id>
The fixer then edits only what that evidence verifies, under its usual keep/revert gate β and the natural follow-up after the fix merges is a re-dispatch of this lane (fresh engine pass) to re-record the now-working quest and replace the video.
Reading the statuses honestly: failed means the sandbox genuinely rejected a step the quest presents as working β prime fix-lane input. skipped is witnessed but classified, not automatically broken: a Windows-path block on a Linux runner or a GUI editor absent from a sandbox is environment-inapplicable, and the recorded detail says so. The review (human or fixer) judges from the detail β the capture never editorializes beyond the transcript.
Whole-stack capture (every step, in a sandbox)¶
scripts/quest/stack_capture.mjs walks a quest's deterministic step plan (scripts/quest/quest_steps.py) in an isolated sandbox and photographs the whole stack after every step: the artifact rendered in headless Chromium at the viewports that step is about, a box-model/DOM probe, page errors, and the command's real exit status. It emits the same agentic-compatible evidence shape, with captures[] on each command β which the recorder turns into the video's browser pane. It calls no model, so a video can be produced at zero AI cost (capture: stack, the workflow's default).
The environment a quest is walked AS comes from its own environment: frontmatter β see ENVIRONMENT_MATRIX.md. The same declaration drives the reader-facing control on the quest page, so page and sandbox never disagree. A worked comparison of a full loop run against the video it produced is in LOOP_VS_VIDEO.md.
Guardrails¶
- Evidence-only replay β the video renders the sealed transcript's commands and outcomes verbatim; no step invents, reorders, or re-runs anything. A quest without evidence gets no video.
- No agent in the lane β everything after the engine is deterministic script; there is no model output anywhere in the render β upload β apply path.
- Human-reviewed publish β unlisted by default; the
quest-videolabel matches no auto-merge policy, so the reference PR always waits for a human. - Idempotent + catalog-guarded β re-runs skip already-published quests (
.quests/videos.yml);--forceis an explicit choice.video_manifest.py applyis an idempotent text edit and never touches anything but thewalkthrough_video:block andlastmod. - Kill switch β unset
QUEST_VIDEO_ENABLEDand the lane is a no-op; the publish leg dies separately with theYOUTUBE_*secrets removed. - Vendored quests β carry
source_repo/source_urland are synced from upstream; do not target them with the publish leg (the apply edit would be overwritten by the next sync and the upstream owner should own their media).
Roadmap (deliberately not built yet)¶
- Scheduled cadence β once trusted, a weekly cron that records the ledger's newly-
perfectslices (the natural "this quest is done, film it" trigger) and a re-record trigger when a quest'slastmodmoves past its video'srecorded. - Narration β a TTS voice-over track composed from the quest's chapter text (needs a voice + licensing decision; the silent side-by-side is already self-explanatory).
- Coverage on the dashboard β a
.quests/DASHBOARD.mdcolumn counting quests with a freshwalkthrough_videoper slice, fed from the catalog. - Level-hub reels β stitching a level's per-quest videos into one βLevel 0001 in 10 minutesβ cut with ffmpeg concat.