DEV Community

Cover image for Dynamo: Give Your Coding Agents Eyes and Your PR Reviewers Proof
Devansh
Devansh

Posted on AI-assisted

Dynamo: Give Your Coding Agents Eyes and Your PR Reviewers Proof

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission πŸ’Έ

What I Built

When an AI coding agent or browser-use sandbox fails in CI at 2:00 AM, everyone flies blind. The agent acts on a headless display without visual feedback, the test runner spits out a 400-line stack trace, and the human reviewer gets a single static PNG that doesn't show which action triggered the regression.

If you try to fix this by feeding raw screen recordings into a Vision-Language Model (VLM), you blow your token budget in under a minute. If you hack together a bash script around Xvfb and ffmpeg, a sudden exception sends SIGKILL, corrupts the MP4 moov atom, and leaves you with zero artifacts when you need them most.

Dynamo Hero Showcase

Dynamo is a headless, zero-dependency Python visual and video harness for coding agents, Playwright browser automation, and computer-use sandboxes. In a single with VisualHarness(): context manager (or a single CLI command), Dynamo:

  1. Manages the headless X11 lifecycle (Xvfb) with a socket readiness probe so your browser never races the display server.
  2. Streams non-blocking screen capture (ffmpeg -f x11grab) with graceful SIGINT finalization β€” even when your agent crashes mid-run.
  3. Logs timestamped action telemetry (telemetry.jsonl) and burns visual click-rings and crosshairs directly into MP4 and looping PR GIFs.
  4. Answers visual questions with model-free arithmetic β€” cropping before encoding, pixel OCR on <canvas> elements, visual diffs, and Set-of-Marks element grounding.
  5. Keeps only the frames that matter β€” scene changes anchored to exact action timestamps β€” instead of paying for a thousand-odd near-identical frames.
  6. Routes surviving keyframes through local open-weight models (qwen3-vl:4b under Apache-2.0 and laya for lightweight typed filtering) so proprietary screens never leave your infrastructure.

Dynamo Autonomous Checkout with Click Telemetry Overlays

Above: A live headless Chromium session recorded inside Dynamo's virtual display (:99), compiled into a two-pass palette GIF with telemetry click rings and action timestamps baked in.


🎬 Video Demo & Visual Showcase

Here is the full product walkthrough demonstrating Dynamo's architecture, crash safety, live autonomous browser execution, and model-free primitives:

πŸ“Ί Watch on YouTube: https://youtu.be/zAtmsYpyuMY

Chapters: 0:00 The Agent Blind Spot β€’ 0:30 The Fatal moov atom Crash β€’ 1:04 One Context Manager SDK β€’ 1:37 Live Headed Chromium Checkout β€’ 2:11 Four Model-Free Primitives β€’ 2:54 Measured Hackathon Benchmark β€’ 3:28 Local VLM Triage (82.6% Token Cut) β€’ 3:58 Production Ready & MIT License.


Who Would Actually Pay for This? (The Side Hustle)

Anyone running autonomous coding agents, QA browser fleets, or computer-use sandboxes in production hits two walls immediately: multimodal inference bills and security compliance.

Dynamo follows an open-core infrastructure side-hustle model built for engineering teams who need visual observability that their security team will actually sign off on:

  • Who buys it: AI dev-shop agencies, QA automation teams running parallel Playwright workers on Kubernetes/EKS, and startups building computer-use agents that interact with sensitive staging environments, internal admin panels, or checkout flows.
  • Why they pay: SaaS session-replay tools charge per-seat or per-minute and require shipping recordings of proprietary code and customer data to a third-party cloud. Dynamo is a self-hosted library with zero data egress and zero GPU requirement for capture.
  • How it monetizes: The core Python SDK and CLI are open-source to drive bottom-up developer adoption. The paid side hustle offers custom sandbox integration, VLM token-budget tuning, and fleet-scale deployment packs (isolated display pools running hundreds of parallel headless sessions per node pool, plus audit-grade compliance artifacts) with direct engineering onboarding.

The Problem: Why DIY Agent Observation Fails

The Failure Modes: Container Crash vs Token Explosion

When an agent fails, hand-rolled shell scripts and naive time-based sampling fail in two catastrophic ways:

  1. Silent Failure & Corrupted Files on Crash: Standard ffmpeg holds the MP4 container index in memory until a clean shutdown. When an unhandled exception or CI job timeout triggers SIGKILL, the header is never written:
   DIY script, agent killed mid-run : moov atom not found    (0 bytes, 0 frames β€” unrecoverable)
   Dynamo,     agent killed         : playable MP4, 4/4 logged actions (SIGINT exit path)
Enter fullscreen mode Exit fullscreen mode
  1. The 38% Duplicate Token Disaster: Naive time-based sampling captures 1 frame per wall-clock second regardless of screen activity. In our benchmark, 19 of 50 consecutive frames were pixel-identical, burning 56,100 tokens on static screens while waiting on network I/O.

The Solution: One Context Manager

Dynamo Architecture & SDK

No daemons to babysit, no DISPLAY variables to export manually, and no ffmpeg flags to memorize:

from agent_visual_harness import VisualHarness

# 1. Enter context: Xvfb starts, readiness probe passes, ffmpeg begins recording
with VisualHarness(display=":99", resolution=(1920, 1080), record=True) as harness:
    # 2. Run browser, agent process, or Playwright tests
    harness.run_on_display(["python", "run_agent.py"])

    # 3. Log actions into telemetry.jsonl
    harness.log_action("click", x=500, y=300, target="#pay-button")

    # 4. Instant screenshot bytes mid-run
    screenshot_bytes = harness.take_screenshot()

# 5. Upon exit (even on exception!), review artifacts are exported cleanly:
artifacts = harness.export(
    video_path="output/repro.mp4",
    gif_path="output/demo.gif",
    vlm_keyframes_dir="output/vlm_frames/"
)
print(f"Generated {len(artifacts.keyframes)} frames for VLM context.")
Enter fullscreen mode Exit fullscreen mode

Or run any existing shell-driven agent or test suite directly from the CLI:

# Start a managed Xvfb display + recorder and run any command inside it
$ dynamo record --display :99 --resolution 1920x1080x24 --fps 24 \
    -- python run_agent.py

# Compile PR-ready artifacts with click overlays baked in
$ dynamo export ./dynamo-session \
    --mp4 output/repro.mp4 --crf 23 \
    --gif output/demo.gif --max-colors 128 --dither bayer \
    --overlay-clicks

# Produce a token-budgeted keyframe set + manifest.json for your local VLM
$ dynamo keyframes ./dynamo-session --scene 0.05 --max-size 1280 \
    --format webp --out output/vlm_frames/
# βœ“ 29 keyframes Β· 1,043 frames scanned Β· manifest.json written
Enter fullscreen mode Exit fullscreen mode

Live Run: Autonomous Headed Chromium Checkout

Live Autonomous Checkout with Telemetry Stream

Inside demo/assets/dynamo_repro.mp4, a headless Chromium browser runs inside Dynamo's virtual display (:99), navigating a multi-step e-commerce storefront:

  • 02.0s: Enters customer shipping address.
  • 06.4s: Types postal code "97" β€” Validator Trap: Storefront rejects invalid 2-digit postal code.
  • 11.6s: Agent inspects page, corrects postal code to "97209", and clicks Continue.
  • 17.1s: Enters credit card details.
  • 25.1s: Clicks Pay β€” Issuer Decline 1: Refused with do_not_honor.
  • 31.7s: Clicks Pay β€” Issuer Decline 2: Refused with do_not_honor.
  • 38.3s: Clicks Pay β€” Authorized! Charge captured on attempt 3 ($429.56).
  • 42.0s: Order Confirmed (#NW-9821) with click overlays timestamped into telemetry.

Four Model-Free Arithmetic Primitives

Recording gets an agent a replay. Four more primitives get it an answer: what error the page showed, which pixels moved, where the button is, and which coordinate space those pixels are in.

These four are pure arithmetic β€” no model involved, which is why they are fast, reproducible, and free:

Four Model-Free Arithmetic Primitives

# 1. Capture a specific region, not the whole screen (crops in ffmpeg before encoding)
$ dynamo capture --region 0,0,480,90 --out banner.png

# 2. Text off pixels β€” for canvas, VNC, RDP, and BIOS surfaces that give up no DOM text
$ dynamo ocr banner.png --find DO_NOT_HONOR --fuzzy   # exits 1 if absent

# 3. Visual regression gate β€” exits 1 when the UI moved
$ dynamo diff before.png after.png --out diff.png

# 4. Number elements so a model answers "12" instead of guessing "(740, 318)"
$ dynamo som shot.png --boxes boxes.json --labels Pay,Cancel --out som.png --resolve 12
Enter fullscreen mode Exit fullscreen mode

Visual Inspection Gallery

1. Region Snip vs. Full Viewport (20Γ— Byte Cut)

Cropping before encoding shrinks a 92 KB full screenshot down to a 4 KB snip. Vision models pricing by image dimensions or bandwidth receive only the error banner:

Region Snip (4 KB) Full Viewport (92 KB)
Region Snip Full Viewport

2. Visual Regression Diff Overlay

Highlights moving UI elements with a red-on-dimmed bounding box overlay. A control comparison with unchanged UI reports zero changed pixels:

Visual Regression Diff Overlay

3. Set-of-Marks Numbered Element Grounding

Numbers elements directly on the live framebuffer so VLMs answer "12" rather than guessing floating-point coordinates:

Set of Marks Grounding on Live DOM Elements

4. Pixel OCR on Non-DOM Surfaces

Extracts text directly from <canvas>, VNC, or RDP frames with fuzzy confusable matching (D0_ $\rightarrow$ DO_):

Pixel OCR Error Extraction


What the Benchmark Actually Shows

Measured Benchmark Matrix: Screenshots vs DIY vs Dynamo

We evaluated the exact same checkout workflow captured three ways β€” plain per-action screenshots, DIY wall-clock ffmpeg, and Dynamo:

Metric A Β· Screenshots B Β· DIY Wall-Clock C Β· Dynamo
Images to the model 24 51 29 (Action-anchored)
Token estimate (@1,100/img) 26,400 56,100 31,900
Bit-identical repeats 1 19 1 (3.6% redundancy)
Mean frame-to-frame Ξ” 3.29 1.76 11.36 (High Information)
Total frame payload 2.4 MB 9.5 MB 901 KB (2.6Γ— Smaller!)
Survives torn-down CI job β€” moov atom not found YES (Playable MP4)
Artifacts generated PNGs only Unindexed raw MP4 + GIF + WebP + Manifest

The honest takeaways:

  • Dynamo and screenshots cost similar tokens (31,900 vs 26,400) because both sample on actions rather than wall-clock time. Dynamo's win is everything screenshots cannot do: the video, the GIF, click overlays, timing manifest, crash resilience, and a 2.6Γ— smaller payload using WebP.
  • Time-driven sampling is the real token disaster. The DIY script kept 1 frame/sec and paid 2.1Γ— tokens with 19 of 50 frames pixel-identical.

Intelligent VLM Triage: Estimated vs. Counted

Three-Stage VLM Triage Funnel

For questions arithmetic cannot answer β€” what did the decline message say? β€” Dynamo routes frames through a 3-stage funnel:

Routing strategy (vision.py) Frames sent Heuristic estimate Counted by qwen3-vl:4b
A Β· every keyframe, whole 29 22,707 tokens 32,132 tokens
B Β· cropped to diff bounding box 29 10,947 tokens 32,040 tokens (99.7%)
C Β· triaged: skip unchanged β†’ laya filter β†’ crop 24 8,575 tokens 26,528 tokens (82.6%)

Key findings:

  1. Cropping saved almost nothing in model tokens (0.3%). Because local VLM servers resize incoming images to a fixed internal token grid, a full frame and a small crop cost the exact same tokens. The crop cuts wire bytes and points the model at what moved, but is not a token saving here.
  2. Skipping frames via a cheap decision model (laya) did the real work. In Mode C, a lightweight typed decision model (Laya) scored candidate frames and dropped 5 low-signal frames before they ever touched the vision encoder. Not sending the frame cut prompt tokens to 82.6% and reduced wall time from 255s to 83s.
  3. 100% Local & Offline: Powered by Apache-2.0 models (qwen3-vl:4b and laya), running entirely on CPU/GPU without cloud API costs or data egress.

Prize Categories

  • Overall Winner (Side Hustle): Dynamo solves an expensive infrastructure problem for teams building autonomous AI agents, backed by transparent benchmarks, a zero-dependency Python SDK/CLI, model-free arithmetic inspection, and a clear B2B open-core monetization path.

  • Best Use of GitHub Copilot / GitHub Actions Integration: Built for native GitHub Actions CI workflows (dynamo record -- pytest tests/e2e $\rightarrow$ dynamo export --gif pr/demo.gif --overlay-clicks) so every pull request automatically receives a visual replay artifact.

  • Best Use of Sentry Agent Tracing / Agent Observability Spirit: Delivers full visual + JSONL action telemetry (telemetry.jsonl), four model-free inspection primitives, and side-by-side estimated vs. actual model token accounting.

Top comments (8)

Collapse
 
autenai profile image
Auten •

The finding that cropping saved 0.3% of model tokens is the most useful line here. Most people assume the crop is the win, and you measured that the local VLM resizes to a fixed grid anyway, so skipping the frame is what actually pays.

One question on action-anchored keyframes: which frame do you keep for an action, the one at the click timestamp or the first one after the screen settles? The click frame usually still shows the old state, and something like the do_not_honor banner can land a second or two later behind a spinner. Do you wait for the scene-change score to go quiet after each logged action, or use a fixed offset? On real desktops we have found "wait until the region stops changing, with a timeout" catches far more of these than any fixed delay, but it costs time on screens with a blinking cursor or an animation.

Collapse
 
devanshdubey profile image
Devansh •
  1. Action Anchors (the "before/at" frame)
    When h.log_action("click", x=500, y=300) is called, the frame at that exact
    timestamp is pinned as an anchor so you never lose the initiating state.

  2. Scene-Change Candidates (the "after/settle" frames)
    ffmpeg's scene filter (e.g. gt(scene, 0.05)) runs across the stream and extracts
    transition points where pixels actually move (e.g., when the spinner resolves).

  3. Merging & Deduplication Window
    Anchor frames and scene cuts are merged by timestamp. Consecutive frames within
    the dedup window with low pixel delta (< threshold) are pruned.

Collapse
 
autenai profile image
Auten •

Thanks, pinning the click frame and letting the scene cuts supply the settled state is a clean split. The reviewer gets cause and effect without you guessing a delay.

One edge case worth a test: a spinner or blinking caret can sit right around gt(scene,0.05). Small animations either never cross it, so the "after" frame is missed, or keep crossing it, so you get a burst of near-identical cuts that dedup then has to clean up. Do you pair each anchor with the first cut after it, or keep every cut inside a window? A "first cut followed by N quiet frames" rule might give you exactly one settled frame per action.

Thread Thread
 
devanshdubey profile image
Devansh •

Haha yeah, that exact edge case is brutal in practice! Blinking carets and looping spinners are the bane of threshold-based scene detection.

Right now, we don’t strictly pair them 1:1. We throw the anchors and scene cuts onto a single timeline, and our dedup pass compares each candidate against the last kept frame (not just the one right before it). That way, if a spinner just oscillates between two visual states, the repeats get caught and pruned pretty quickly. The trickier side of the coin was tiny spinners (like a 20px icon in a 1080p window) that never even cross scene > 0.05β€”which is why we ended up adding localized diffs between actions.

That said, your idea of β€œfirst cut followed by N quiet frames” is honestly brilliant. It’s basically a visual debounce, and it’d give you a guaranteed, clean β€œcause
β†’
β†’ settled effect” pair per action without needing to tidy up bursts in post.

I’m actually going to write a test case for a 1Hz blinking cursor to play with that settling window logic. Really appreciate the sharp catch!

Thread Thread
 
autenai profile image
Auten •

Comparing against the last kept frame is a good call. One thing the 1Hz caret test may show: if the spinner flips between two states, A then B then A, each candidate still differs from the last kept frame, so you can keep every flip. Checking a candidate against the last two or three kept frames, or ignoring changes smaller than a few dozen pixels unless they are near the anchor's click point, usually stops that.

Localized diffs between actions for the 20px icon case makes a lot of sense. Curious what the caret test turns up.

Thread Thread
 
devanshdubey profile image
Devansh •

You’re 100% right on the A β†’ B β†’ A ping-pongβ€”comparing only against the immediate previous kept frame totally falls for that trap if the cycle is slower than our min_gap (0.3s).

Right now, our safety net for that is actually downstream in the triage layer: we set a min_region_px = 400 threshold, so glyph-level changes like a 2Γ—18px caret get flagged as noise and don't trigger model calls.

But having the keyframe reducer itself be smart enough not to capture those flips in the first place is way cleaner. A rolling ring buffer of the last 2–3 kept frames or anchoring changes to the click locus would kill the flip loop right at the source.

Definitely stealing the ring-buffer idea for the reducer pass. Really appreciate you thinking through these edge cases with me!

Thread Thread
 
autenai profile image
Auten •

One thing to watch with min_region_px = 400: a checkbox or radio toggle is roughly 16x16 = 256 px, so the triage layer would drop exactly the change that proves a click landed. If the threshold only applies away from the click point, you keep the caret filtered and still see the checkbox flip.

Good luck with the 1 Hz caret test.

Thread Thread
 
devanshdubey profile image
Devansh •

πŸ‘