iris studio's manifest
iris studio makes images from FLUX.2 Klein, FLUX.3 Image Klein and Z-Image through a native Metal engine in C behind a Go server — the same shape as h3 studio, for stills. Its repository ships this manifest, and the registry entry carries a copy so the studio can be described before it is fetched. It passes five of the seven criteria a manifest can answer: it declares neither memory nor disk, and no test profile.
The file is studios/iris-studio.yaml, shown in full. A note follows the line it explains.
# A registry entry: a pointer at iris studio's repository
# (schema/registry-entry.json, docs/design/05-sdk-and-custom-studios.md §5a).
#
# The manifest belongs in the studio's own repository as helmstudio.yaml, beside
# the code it describes, and iris studio ships one. This copy is not dead weight
# for that: resolution reaches nothing over the network (M7 Q7), so before a
# studio is fetched this is the only manifest there is, and an entry without one
# shows on the shelf as `not_fetched` — no description, no hue, no checkpoint to
# choose. So the copy stays, and the thing that matters about it is that it
# agrees with the repository. One that had drifted described iris before it ran
# under helmstudio at all: `capabilities: []` meant the daemon issued no token,
# and a `cmd` with no `--root {data}` gave it nowhere to keep sessions, so the
# studio exited 2 on every launch. An installed studio is launched from its own
# checkout's manifest, which is why that only bit a machine installing fresh.
#
# Where the inline manifest sets id, repo or ref, each must equal this entry's;
# the validator enforces it. There is no `certified` field: a level is derived
# from what has actually been checked, never declared by the thing being
# checked (docs/decisions.md M7 Q4, Q15).
id: iris-studio
The id the studio is known by everywhere, and the file is named for it.repo: https://github.com/janishar/iris-studio
Where helmstudio clones from, with git.ref: main
A branch, so an install takes whatever it points at when the clone runs. A registry entry normally pins a commit; docs/decisions.md 2026-09-19 says why this one does not.manifest:
A copy of the helmstudio.yaml the repository ships. Resolution never reaches the network, so this is the only manifest there is until the studio is fetched — an entry without one is shown as not fetched. What matters is that it agrees with the repository; one that had drifted described iris before it ran under helmstudio at all. Its id, repo and ref must equal the pointer's. id: iris-studio
The same id as the pointer's. name: iris studio
The name people see. description: Native Metal image generation across the FLUX.2 Klein, FLUX.3 Image Klein and Z-Image checkpoints.
One sentence saying what the studio does. kinds: [image]
Images. # helmstudio 03 §2's identity hue for this studio.
hue: { dark: "#d8558f", light: "#b12f68" }
Its identity colour in each theme: a stripe, a dot and its clips on the timeline in the launcher, and the accent on its own page. license: MIT
The studio's own licence. repo: https://github.com/janishar/iris-studio
The same repository as the pointer's. submodules: true
The engine, iris.c, is a submodule, and the first build step runs inside it. peak_ram_gb: 35 # measured: FLUX.3 distilled at the default 1024x1024 on a 64 GB M5 Pro; 85 at 1792x1792. FLUX.2 and Z-Image not measured. The prompt upsampler peaks at 21 and exits before iris loads.
Measured on FLUX.3 distilled at the default size, as the comment says. FLUX.2 and Z-Image have not been measured. The prompt upsampler peaks at 21 GB and exits before iris loads, so it does not add to this. requires:
An Apple Silicon Mac and five tools. No memory or disk is declared, which is why criterion 2 fails. uv is not one of the five: only the optional upsampler needs it. os: [darwin]
arch: [arm64]
tools: [git, make, gcc, xxd, go]
runtime:
A native kernel on Metal, in C. framework: native-kernel
backends: [metal]
precision: [bf16]
language: c
# What iris studio actually uses, and no more (criterion 9). A finished take is
# adopted as an asset and recorded as one gallery item, and a render is
# reported as a task job with its progress and its log, so the launcher can see
# what this studio is doing. There is no timeline: a sequence is an edit of
# video clips, and this studio makes stills. Sessions stay iris studio's own
# directories under {data}, so kv and records are not requested and a gallery
# item carries no session_id.
capabilities: [assets, gallery, jobs]
Three. A finished take is adopted as an asset and recorded as one gallery item, and a render is reported as a task job with its log. No timeline: a sequence is an edit of video clips and this studio makes stills. This is also what earns it a token — a studio declaring none gets none, and iris refuses to start without one. # The page loads helm-ui for the shared gallery and the render's log; it still
# draws its own takes rail, preview and terminal, so this pins what it loads
# and nothing else.
sdk: { runtime: "^1", ui: "^1", css: "^1" }
The majors its page loads through the /helm/ proxy: the runtime client, the components it draws helmstudio's gallery and the render log with, and helm-css. # Contacted at build or run time, which is what the field means and what the
# approval screen shows. The Go proxy and the checksum database are here
# because `go build` resolves helm-runtime-sdk through them; Hugging Face is
# where the weights come from. pypi.org and files.pythonhosted.org are where
# pup's uv sync gets its packages, and github.com is there because pup's uv.lock
# pins diffusers to a git revision.
network: [huggingface.co, cdn-lfs.huggingface.co, proxy.golang.org, sum.golang.org, pypi.org, files.pythonhosted.org, github.com]
The hosts it is expected to contact: Hugging Face for its weights, and the Go module proxy because the second build step resolves helmstudio's runtime SDK through it, pypi.org and files.pythonhosted.org for the upsampler's packages, and github.com because the upsampler's lock pins diffusers to a git revision. build:
Three steps, in order: the engine, built with make in its submodule, the Go server, then the prompt upsampler's Python environment. The last is optional: if it fails, uv missing included, it removes the environment it left, is skipped, and the studio runs without Upsample. There is no python block because one would refuse to launch the studio without that environment. - name: Build the iris engine
cwd: iris.c
run: make mps
- name: Build the studio server
cwd: .
run: go build -o iris-studio .
# Optional, and no python block: a block would refuse to launch the studio
# without this environment, and all a failure here costs, uv missing included,
# is Upsample. So uv is not in requires.tools either. A failed sync leaves a
# .venv with a python and no packages, and the studio would offer Upsample on
# that python, so it goes.
- name: Sync the prompt upsampler
cwd: pup
run: uv sync --locked || { rm -rf .venv; exit 1; }
optional: true
# One checkpoint at a time: iris loads the directory --model points at. The
# FLUX.2 and Z-Image entries are the ones download_model.sh produces. FLUX.3
# keeps its repo's name, and its files list leaves out the root's single-file
# weights and variants/, which iris does not read and would double the
# download. Each is selectable, so {models.selected} is whichever one is chosen
# in the launcher.
weights:
Seven checkpoints, all selectable: one is chosen before install, install downloads only that one, and the others are fetched or linked when they are wanted. The eighth weight is the prompt upsampler, optional, so install leaves it out and only the API's fetch route downloads it; no launcher screen does, so a person downloads it themselves. The command does not name it, because an unfetched optional weight refuses any command that does; the studio looks for it beside the checkpoint, or where its Paths dialog points. - { name: flux_klein_4b, repo: black-forest-labs/FLUX.2-klein-4B, dest: flux-klein-4b, selectable: true }
- { name: flux_klein_4b_base, repo: black-forest-labs/FLUX.2-klein-base-4B, dest: flux-klein-4b-base, selectable: true }
- { name: flux_klein_9b, repo: black-forest-labs/FLUX.2-klein-9B, dest: flux-klein-9b, selectable: true }
- { name: flux_klein_9b_base, repo: black-forest-labs/FLUX.2-klein-base-9B, dest: flux-klein-9b-base, selectable: true }
- { name: zimage_turbo, repo: Tongyi-MAI/Z-Image-Turbo, dest: zimage-turbo, selectable: true }
- { name: flux_3_klein, repo: black-forest-labs/flux-3-image-klein, dest: flux-3-image-klein, size_gb: 21, files: ["transformer/**", "text_encoder/**", "vae/**"], selectable: true }
- { name: flux_3_klein_base, repo: black-forest-labs/flux-3-image-klein-base, dest: flux-3-image-klein-base, size_gb: 21, files: ["transformer/**", "text_encoder/**", "vae/**"], selectable: true }
# The prompt upsampler. Optional, so it is not in the command (an unfetched
# optional weight refuses any command naming it): the studio looks for it
# beside --model, which for a linked checkpoint is the directory it links to,
# or wherever Paths points.
- { name: pup, repo: black-forest-labs/flux-3-image-klein-pup, dest: flux-3-image-klein-pup, size_gb: 19, optional: true }
processes:
One process: the Go server, which drives the engine. - name: studio
role: main
heavy: true
It holds the model. cmd: "./iris-studio --iris ./iris.c/iris --model {models.selected} --port {port} --root {data}"
{models.selected} becomes the path of whichever checkpoint was chosen. {data} is the directory the studio keeps sessions in; it makes none of its own and will not start without one, and a take has to be inside it for helmstudio to adopt it. port: { prefer: 8720 }
health: { tcp: true, timeout_s: 30, interval_s: 2 }
A TCP connection on its port is enough to call it ready. busy: { path: /api/busy }
Where the launcher asks whether the studio is mid-render before offering to stop it. /api/busy answers that contract with a busy flag and the render's progress. ui: /