Repository navigation
Conversation
…heckout Phases 0–2 of apps/renderer-service/PLAN.md. - Remove the inline virtualMedia path (job-processor, render-upload, cli, /run and /status), the unused DI container, and BASE_URL/R2_* config. - Fix the old gatewai-renderer paths; the entrypoint starts the Node server once and runpod_worker.py is now a thin localhost client. - Config: one validated schema reporting every problem at once; secrets are scrubbed from process.env before repo code can run. - Job API v2: zod schema, allowlist/bucket/env checks, stable error codes with retryable flags, redaction, single-slot queue, job store with TTL, /v2/jobs with bearer auth, SSE progress and cancel. - Git checkout: per-worker mirror cache with corruption re-clone, ref resolution pinned to one SHA, reusable workspace slots, sparse checkout, allowlisted submodules, LFS pull with disk preflight and pointer postflight, unsupported-filter detection, per-host credential helper and GitHub/GitLab/Bitbucket/Gitea error mapping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Detect the install root and package manager (packageManager field or lockfile; pnpm, npm, yarn classic/berry) and install through corepack with a frozen lockfile and shared stores on the cache volume. - Skip the install when the project only needs framefields plus tooling; skip it on warm workspaces whose install stamp still matches. - Check the declared framefields range against the bundled engine (FF_ENGINE_POLICY) and warn on engines.node mismatches. - Map install failures to INSTALL_FAILED / INSTALL_TIMEOUT / DISK_FULL / LOCKFILE_MISSING with hints (stale lockfile, registry auth, native build); retry once on registry outages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- renderVideo takes `signal` (checked between frames and before the audio mix; rejects with signal.reason) and `onProgress` (per frame, then audio). framefields' RenderVideoOptions passes both through. - resetRenderCaches() drops path-keyed caches (media inputs/decoders, textures, LUTs, fonts) while keeping the GPU device warm; InputStore gains clear(). A long-lived worker needs this because the next job's checkout can hold different bytes at the same path. - GPU test (FF_GPU_TESTS=1) covering progress, abort, and the stale-path case before/after the reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Engine alias (module.registerHooks): framefields / @framefields/* imports
and requires from a workspace resolve to the worker's own ESM build, so
repo code shares the warm GPU stack. Workspace sources always load as ESM;
in CJS mode tsx would transpile a second engine copy.
- Per-job loading: tsx register() with the project's tsconfig and a native
import of the entry tagged ?ff-job=<id>, propagated to workspace modules
only, so changed code at the same path is never served from cache.
- Export shapes: a Composition, a record of named compositions (with
target.select), or { outputs: [...] } render plans; video/audio, frames,
frame grid and LUT outputs.
- Job sandbox: cwd, env, argv and a process.exit trap, restored after each
job; stray rejections fail the active job instead of the worker.
- Stall detection, abort grace period, event-loop watchdog (helper thread
kills a blocked process), crash journal reported after restart, and
recycling by job count / heap / stray errors / leaked handles / GPU loss.
The RunPod shim sets refresh_worker while the worker drains.
- Tests: real-Node loader harness (alias identity incl. CJS packages, module
freshness, error classes, exit trap), watchdog kill, and a GPU harness
rendering six jobs back to back (FF_GPU_TESTS=1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Verify each file: exists, non-empty, within FF_OUTPUT_MAX_BYTES, SHA-256, and ffprobe for media with the engine's frame count (±1). - QA gate: render.qa.failOn matches issue codes or severities; outputs are still delivered (unless FF_QA_BLOCK_UPLOAD) and listed in QA_FAILED. - Sinks: s3 (multipart via lib-storage, ff-* metadata, public URLs), presigned PUT (one URL per output), local (atomic rename inside FF_OUTPUT_DIR), inline (base64 under FF_INLINE_MAX_BYTES). Retries for transient failures; UPLOAD_AUTH_FAILED / UPLOAD_URL_EXPIRED fail fast. - Idempotency: a manifest per idempotencyKey; a later attempt for the same commit with all objects present skips install/render/upload. - SSE: the terminal event is flushed before the stream closes. - Tests against an in-memory S3/presigned server, plus ffprobe checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- cli.ts: one job in-process from --job-file or stdin; result to
--result-file or a final {"ffResult": …} line; exit 0/1/2.
- worker/boot.ts: config, engine alias, guards, journal, watchdog, janitor
and recycling shared by the HTTP server and the CLI.
- CacheJanitor keeps FF_WORK_DIR / FF_CACHE_DIR under their limits between
jobs: LRU workspaces; then LFS objects, git mirrors (other workers' only
when idle >1h), package caches; never the next job's repo or the models.
The LFS disk preflight evicts on demand before failing with DISK_FULL.
- Fix a lost-update race in the directory size walk.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ase 8) - Move Dockerfile.renderer to apps/renderer-service/Dockerfile and the entrypoint to docker/entrypoint.sh. Runtime gains git, git-lfs, ssh, tini (PID 1) and a HEALTHCHECK; VARIANT=full adds a toolchain; runs as user framefields; modes http | runpod-serverless | cli. - Pinned SSH host keys for github.com, gitlab.com and bitbucket.org, verified at build time against the published fingerprints. - RunPod serverless and pod templates plus a saveTemplate script; a docker-compose setup with MinIO; a GHCR release workflow. - Config: default cache dir prefers /runpod-volume, then /var/cache/framefields. - README rewritten as the self-hosting guide. Not built yet: no Docker daemon on the dev machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The image no longer bundles or builds framefields. Each repo installs the framefields version it pins (package.json + lockfile) and the worker renders with that install. - Engine binding: the first job imports its project's framefields and initialises the GPU; later jobs on the same version share it (imports from any workspace are routed to the bound engine). A different version, or new dependencies in a workspace already loaded, fails with retryable ENGINE_SWITCH and the worker recycles so the retry gets a fresh process. - Older engines without worker hooks still render; no stall detection, and the worker recycles after each job to avoid stale caches. - Projects must depend on framefields (ENGINE_NOT_FOUND); the "no install" fast path is gone. Only project sources get the per-job module token and forced ESM; node_modules keep their format and stay shared. - Service drops all monorepo runtime deps (own pino logger); framefields and @framefields/renderer are test-only devDependencies. - framefields exports resetRenderCaches(). - Image: worker only (turbo prune → install --ignore-scripts → tsc → pnpm deploy --prod), on node:24-trixie-slim because prebuilt Dawn needs glibc >= 2.38. Readiness = tools present; the engine is reported in /health. - Fixes found running it in Docker: EPIPE from writing stdin to commands that don't read it; corepack's unpinned npm vs the lockfile's npm; install errors now name the real cause instead of npm's usage text. Verified in the built image (arm64, software Vulkan): a repo pinned to framefields@2.1.3 from npm installs, binds, renders VP9 and delivers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
H.264/HEVC renders failed on linux-arm64 with "Encoder 'h264_rkmpp' cannot be used for quantizer-based encoding". @mediabunny/server uses Codec.findEncoder(id) as its software encoder, and node-av's linux-arm64 FFmpeg build registers h264_rkmpp/hevc_rkmpp before libx264/libx265. NVIDIA GPUs without NVENC (A100, H100) would fail the same way, since the hardware encoder was used without checking it works. - video-encoders.ts: make libx264/libx265/libvpx/SVT-AV1 the default encoders, and pass a hardwareContext resolver that opens the platform's hardware encoder and encodes one frame before using it (once per codec). node-av is imported natively from @mediabunny/server's resolution so patches and HardwareContext instances match its copy. FRAMEFIELDS_HW_CODECS=off forces software. - renderVideo always asks for "prefer-hardware"; the resolver decides (AMD keeps software). - device.ts: fall back to a software WebGPU adapter when no GPU adapter exists; describeGpu() reports the adapter and whether it is a CPU one. - Changeset (minor) also covers the earlier worker hooks: renderVideo signal/onProgress and resetRenderCaches(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The image ships Mesa's CPU Vulkan driver, so a GPU machine whose Vulkan driver isn't mounted silently rendered on the CPU. - FF_GPU=auto (default): a machine with a GPU renders on it (Vulkan is limited to the GPU's ICDs) and fails with GPU_UNAVAILABLE if it can't; a machine without one renders on lavapipe. require: GPU only (RunPod templates). cpu: lavapipe only. - After the engine binds, the actual WebGPU adapter is checked (via framefields' describeGpu() when available); a CPU adapter on a GPU machine fails the job and marks the worker unhealthy, and the RunPod shim refreshes the worker on GPU_UNAVAILABLE. - /health components.gpu and results' engine.gpu report where it ran. - README: GPU and CPU rendering section, NVENC GPU guidance; PLAN §8.7. Verified in the arm64 container: H.264 job renders with libx264 on lavapipe using the fixed framefields; FF_GPU=require fails readiness. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Add the output module (verify, sinks, deliver): the root .gitignore's **/output/ rule had kept apps/renderer-service/src/output out of git, so CI couldn't build the service. - Push to main publishes :main and :sha-<short>; renderer-v* tags publish :<version>, :<major.minor> and :latest; -full variants alike. Pull requests build and smoke-test only; manual runs publish by default. - Smoke test also boots the worker and requires /health ok on CPU. - README/PLAN: published tags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LFS end-to-end tests with real git-lfs (file:// origins): cold download
into the shared store, warm hit, objects shared across repos, include
patterns + LFS_POINTERS_LEFT, LFS off, LFS_OBJECT_MISSING. They found two
bugs with local repos:
- `-c lfs.storage` also redirected git-lfs's file:// transfer agent, which
then looked for the remote's objects in our store ("remote missing
object"). lfs.storage now goes in the repo config.
- The agent drops GIT_CONFIG_GLOBAL and -c but reads $HOME/.gitconfig, so
a mounted repo owned by another user failed with "dubious ownership".
The session's isolated config is now $HOME/.gitconfig, with
safe.directory for file:// repos.
Verified in the arm64 container: a repo with an LFS-tracked framefields
tarball and logo installed from LFS and rendered the logo.
/health now reports both disks (free, total, eviction threshold,
lowOnSpace), from a snapshot refreshed in the background at most every
10 s. Test timeout raised to 30 s for real git/LFS work under load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Changesets pre mode (beta): all packages 2.2.0-beta.0. framefields is published under npm's `beta` dist-tag (release:framefields:beta), so a plain `npm i framefields` keeps installing the stable 2.1.x. - renderer-image: prerelease tags (renderer-v2.2.0-beta.0) publish :2.2.0-beta.0 and :beta only, never :latest or :major.minor. A tag push runs the workflow from the tagged commit, so this branch can publish before it is merged. - Renderer docs point projects at framefields@beta; RunPod templates and compose use the :beta image until the first stable renderer release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…izes examples/smoke-project is a ready-made repo for testing a deployment, on framefields@beta (2.2.0-beta.0). Exports: film (title, media, effects, charts scenes with push/wipe/iris transitions, score, grain), threeD (studio environment, shadows, bloom, orbiting camera, primitives, extruded text, glTF), vision (ONNX matte, pose, boxes), all (three outputs), plus each scene alone. Covers text animators, mattes, flex, gradients, shapes with trim paths, image + LUT + curves, SVG, Lottie, video, video-in-type, ten effects, charts, SRT captions and audio. Generated assets (glTF, Lottie, captions, a 17³ LUT) are committed; the repo's media policy keeps fonts and footage out of git, so scripts/copy-media.sh copies them from a local checkout. package.json lists allowScripts: npm 11.19 skips unapproved install scripts, and framefields' native dependencies need theirs. Verified locally (Metal) and through the worker container (LFS repo, inline output). The container run showed Docker Desktop host mounts reporting a 117,870 GB disk with 7.8% free, which made every job evict everything. The janitor now caps percentage thresholds at 200 GB and treats disks above 64 TiB (or free > total) as not measurable: no free-space eviction there, /health reports measurable: false, and the optional GB caps still apply. .dockerignore: nested node_modules and the renderer examples are left out of the build context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r job - The generated glTF gem now has TEXCOORD_0. Without it the model drew nothing on Linux/Vulkan (it renders on Metal); that engine bug is tracked separately, and the smoke test shouldn't trip on it. - README: FF_INLINE_MAX_BYTES applies to a job's outputs together; `all` returns ~7 MB and needs a raised limit or S3 (verified: the default failed with OUTPUT_TOO_LARGE after rendering). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- RunPod starts the container through its own init, so tini isn't PID 1 and warned that zombie reaping is off; run it with -s. - README/PLAN: RunPod RTX 3070 results (GPU detected, statfs accurate on the container disk, threeD 21 s cold, film 16 s warm, vision 388 s on CPU ONNX) and the NVENC finding: node-av 6.1.1's FFmpeg needs NVENC API 13.1 (driver >= 610); on driver 580 framefields falls back to libx264. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- node-vision keyed its one shared runner on every option, so layers that differed in classes/confidence closed and recreated their ONNX sessions each frame. Runners are now keyed by model options, and decode options get a VisionRunner.derive() over the same sessions. This also fixes the "thread constructor failed" crash with onnxruntime-web installed. - Layers showing the same pixels share one RTMDet-Ins pass (content hash); runs of one model are queued. - onnxruntime-web is an optional dependency of framefields. Dawn's GPUSupportedFeatures gets the forEach/values/entries ORT-web calls; software adapters (lavapipe, SwiftShader) fall back to the CPU EP. - CPU sessions on Linux size intra-op threads from the CPUs the process may use (cgroup quota); FRAMEFIELDS_VISION_THREADS overrides. - FRAMEFIELDS_VISION_PROVIDER=auto|webgpu|cpu|cuda forces a backend. Smoke project `vision` export on an Apple Silicon Mac: 84.9 s before; 41-43 s on the CPU and 26-29 s on the GPU after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changesets pre mode (beta): all packages 2.2.0-beta.1, carrying the vision GPU change (vision-gpu-shared-sessions). Measured in the renderer worker on a RunPod RTX 3070: vision export 58.7 s on 2.2.0-beta.0, 22-25 s with this build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the network-volume cache RunPod Serverless returned 500 for every result POST: the platform's job-done URL had no query string and the SDK appends &isStream=..., so finished jobs were re-queued. The shim adds the missing ? before importing the SDK and logs the URL's shape. Network volumes report 2.3 PB, so the serverless template sets FF_CACHE_MAX_GB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes
apps/renderer-serviceself-deployable. You send a job naming a repo, a ref and an exported composition. The worker:framefieldsversion.It runs on RunPod (serverless or pod), with plain Docker, or as a CLI. The full spec is in
apps/renderer-service/PLAN.md; the setup guide is in itsREADME.md.Worker (
apps/renderer-service)retryableflag,/v2/jobs, progress over SSE, cancel, bearer auth. Jobs run one at a time.framefields; there's no engine in the image.ENGINE_SWITCH, and the worker restarts.process.exitis trapped.FF_GPU):auto(default): a machine with a GPU must render on it; a machine without one renders on the CPU (lavapipe).require(RunPod templates): GPU only.cpu: CPU only.GPU_UNAVAILABLE, and RunPod replaces the worker.framefields (minor changeset → 2.2.0)
renderVideoacceptssignalandonProgress;resetRenderCaches()clears caches between jobs in one process.h264_rkmpp. They would also fail on GPUs without NVENC, such as A100/H100. Now libx264, libx265, libvpx and SVT-AV1 are the defaults, and a hardware encoder is used only after a one-frame test passes.FRAMEFIELDS_HW_CODECS=offforces software.describeGpu()reports which adapter is in use.Testing
FF_GPU_TESTS=1) pass on macOS Metal.FF_GPU=requirefails the health check withGPU_UNAVAILABLE.🤖 Generated with Claude Code