Vibecode Flair AI
track this build4 phases, 12 steps, beginner friendly0%The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Flair AI, compose product scenes from cutouts, props, and generated backgrounds. The hard boundary is specialized product-photo workflow, templates, model quality, and hosted rendering, plus frontier models, compute, and data.
You are building a lean indie version of Flair AI. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Flair AI · indie build A local workspace for product scenes that drives your own model server, whose hard problems are keeping the real product pixels untouched, recording enough of each job that next month's rerun produces the same picture, and never letting a licence you did not read decide whether you can sell the result. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local service with a small frontend, bound to localhost, no accounts | this is a workstation tool and the GPU is next to you | | Backend | Your own model server called over its HTTP API, one endpoint in configuration | the generation engine is a dependency, not something to reimplement | | Compositing | The masked product pasted back over the generated scene at full resolution | the scene may be invented and the goods may not | | Provenance | Workflow graph, seed, sampler settings and model file hashes stored with every output | a seed alone does not reproduce anything | | Registry | Every model file recorded with its licence, source and a commercial flag | the licence question arrives when the picture is already on a listing | | Storage | Outputs on local disk with a sidecar per image, a ceiling and a retention rule | a batch run fills a disk faster than anyone expects | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs the service, the job queue and the compositing. - Get it: python.org or your package manager. - Verify: python3 --version prints 3.12 or higher - [ ] **A local generation backend with an HTTP API** · free - Why: This build submits jobs and stores provenance; it does not implement diffusion, and the backend is the one dependency it cannot work without. - Get it: Install ComfyUI or an equivalent server that exposes a job API on your own machine. - Verify: A minimal workflow submitted over HTTP returns an image - [ ] **A machine with enough video memory for your chosen models** · free if you own it, hourly if rented - Why: The memory estimate feature exists because the alternative is discovering the limit during a two hundred image batch. - Get it: A card with the memory your chosen checkpoint needs, or a rented machine you control. - Verify: A single generation at your target resolution completes without swapping - [ ] **Model files whose licences you have read** · free, subject to the licence - Why: The licence determines whether the output can be used commercially, and the terms for some popular weights have been revised more than once. - Get it: Download the weights and save the licence text and URL beside each file. - Verify: Every model in the registry has a licence, a source URL and a commercial flag - [ ] **Your mask threshold, retention and commercial flags** · free - Why: These three decide what is shippable, what is deleted and what can run, and each is enforced in code. - Get it: Write down the permitted mask difference, how long non-favourites are kept, and which projects are commercial. - Verify: All three appear in the configuration and in tests ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. - specialized product-photo workflow, templates, model quality, and hosted rendering - frontier proprietary models - hosted GPU capacity - licensed training data - moderation and fast global delivery If one of those is essential to you, that is the reason to keep paying for Flair AI, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Flair AI The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local product-photography workspace that drives your own model server and records how every image was made to replace Flair AI. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two rules, and both of them decide whether the picture is usable rather than whether it is good. The licence on the weights decides whether you may sell what comes out. FLUX.1 [dev] is under a Non-Commercial License, version 2.0 dated November 2025, whose non-commercial purpose is defined as receiving no direct or indirect payment arising from use of the model, while FLUX.1 [schnell] is released under Apache 2.0 (both checked). So the model registry stores a licence and its source for every file, each project is marked commercial or not, and a non-commercial model cannot run in a commercial project. Read the licence of the exact weights you downloaded, since these terms have been revised more than once. Second, generate the scene, never the product. The surface, the light and the background are yours to invent; the item a customer receives in a box is not, and a redrawn label or an invented logo is a misdescribed good. The real photograph is composited back over the generated scene at full resolution, and any output where the product pixels moved is flagged, not shipped. ### Stack (fixed, do not substitute) - Runtime: A local service with a small frontend, bound to localhost, no accounts · this is a workstation tool and the GPU is next to you - Backend: Your own model server called over its HTTP API, one endpoint in configuration · the generation engine is a dependency, not something to reimplement - Compositing: The masked product pasted back over the generated scene at full resolution · the scene may be invented and the goods may not - Provenance: Workflow graph, seed, sampler settings and model file hashes stored with every output · a seed alone does not reproduce anything - Registry: Every model file recorded with its licence, source and a commercial flag · the licence question arrives when the picture is already on a listing - Storage: Outputs on local disk with a sidecar per image, a ceiling and a retention rule · a batch run fills a disk faster than anyone expects ### Data model (create this before Phase 1) - models(id, filename, sha256, kind, licence, licence_url, commercial_ok, added_at) · kind is checkpoint, lora, vae or upscaler - projects(id, name, commercial, product_asset_id, created_at) · the commercial flag is what the licence gate reads - assets(id, project_id, kind, path, sha256, width, height) · kind is source_photo, mask, prop or reference - jobs(id, project_id, workflow_sha256, workflow_json, seed, sampler, steps, cfg, model_ids_json, backend_version, state, attempts, started_at, finished_at, last_error) - outputs(id, job_id, path, sha256, width, height, mask_diff_pct, sidecar_path, favourite, note) - batches(id, project_id, plan_json, total, completed, failed, created_at) · so a run that dies at image sixty knows which sixty exist - An output is shippable only when every model in its job is permitted for that project's commercial flag and its mask difference is under the stated threshold. ### Phase 1 · A cutout that keeps the actual product Build: Cut the product out to an alpha mask, with manual repair for the edges the automatic pass gets wrong, and store the mask as an asset. Generate the scene, then composite the original product pixels back over the result at full resolution. Measure the difference inside the mask between the source and the final image, and flag anything above a stated threshold. Trap: Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. Done when: The composited output is pixel-identical to the source photograph inside the mask, apart from an explicitly permitted edge blend measured in pixels. A deliberately over-strong generation is flagged by the mask difference check and cannot be marked shippable, tested. Mask edits are stored as an asset so a later rerun uses the same mask. A source photograph with transparency already present is handled rather than silently flattened. Do not build yet: No batches, model registry, contact sheet or export. ### Phase 2 · Jobs you can actually run again Build: Submit to your own model server over its HTTP API and poll for the result. Store the full workflow graph, its hash, the seed, sampler, steps, guidance, the file hash of every model involved and the backend version with the job. Offer a rerun that resubmits exactly that graph, compares the output hash, and reports honestly when the result differs. Trap: Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. Done when: A rerun of a stored job on an unchanged backend reproduces the same output hash, tested. Replacing a model file with different contents under the same name causes the rerun to report a hash mismatch rather than proceeding quietly. The backend version is recorded with every job and shown beside the output. A job submitted while the backend is down fails with a clear reason and can be resubmitted without re-entering anything. Do not build yet: No licence gate, batches or contact sheet yet. ### Phase 3 · The licence gate and the missing-file problem Build: Register every model file with its licence, the URL you read it at, and whether it permits the commercial use you intend. Mark each project commercial or not. Refuse a job whose models are not permitted, with the licence and its URL in the refusal. Verify every model referenced by a workflow exists by hash before submitting, and estimate the memory the run needs so a failure is predicted rather than discovered. Trap: A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. Done when: A job in a commercial project using a model marked non-commercial is refused with the licence name and URL shown, tested. A workflow referencing a missing model is refused before submission rather than running with a substitution, tested by removing a file. Every registered model records a licence and the URL it was read at, with no blank values permitted. The memory estimate appears before a run and a run that exceeds available memory is warned about. Do not build yet: No batches, contact sheet or export yet. ### Phase 4 · Batches that survive the hardware Build: Plan a batch as variants across props, angles, aspect ratios and seeds, writing the plan before anything runs. Execute one job at a time against the backend with a per-job timeout, recording each result as it lands. Detect a backend that has crashed or run out of memory, wait for it to return, and resume from the first incomplete item. Show progress and elapsed cost in time. Trap: Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. Done when: Killing the backend mid-batch and restarting resumes at the first incomplete variant with nothing produced twice, tested. A per-job timeout releases the queue and marks that variant failed rather than stalling the batch. The batch plan is written before execution and shows completed against total throughout. A failed variant can be retried individually without rerunning the batch. Do not build yet: No contact sheet, sidecars or export yet. ### Phase 5 · The contact sheet, the sidecar and the disk Build: A searchable contact sheet with compare, favourite, annotate and rerun, filtered by project, model and licence status. Export an image with a sidecar file recording the workflow hash, seed, models and their licences, plus a note that the scene was generated and the product was not. Set a disk ceiling and a retention rule for non-favourited outputs, and make deleting a project remove its files as well as its rows. Trap: Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together. Done when: Every exported image has a sidecar naming the workflow hash, seed, models and licences, checked by opening one. The contact sheet filters by licence status so non-commercial outputs can be found in one click. Non-favourited outputs past the retention are deleted and the deletion is logged. Deleting a project removes its files and its rows, asserted in a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Training or fine-tuning a frontier model, which is a hardware budget and a dataset problem rather than an application one. - Copying a vendor's proprietary model or its training data, which is both the thing you cannot do and the thing that makes the subscription worth paying. - Hosting generation for other people, which brings moderation, abuse handling and a GPU fleet you would have to keep fed. - Generating the product itself rather than the scene around it, because a redrawn label on an item for sale is a misdescription of goods. ### README must contain - That the licence of each model file is recorded with its source URL, and that a non-commercial model is refused in a commercial project - That the scene is generated and the product is composited back unchanged, with the mask difference measured on every output - That every job stores its workflow graph, seed and model hashes, and that a rerun reports a mismatch rather than hiding it - How a batch resumes after the backend dies, and where the plan is written - Where outputs and sidecars live, what the retention rule deletes, and what deleting a project removes ===== AGENTS.md ===== # Agent instructions · Flair AI indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local service with a small frontend, bound to localhost, no accounts, Your own model server called over its HTTP API, one endpoint in configuration, The masked product pasted back over the generated scene at full resolution, Workflow graph, seed, sampler settings and model file hashes stored with every output, Every model file recorded with its licence, source and a commercial flag, Outputs on local disk with a sidecar per image, a ceiling and a retention rule. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. ===== BUILD_PLAN.md ===== # Build plan · Flair AI A local workspace for product scenes that drives your own model server, whose hard problems are keeping the real product pixels untouched, recording enough of each job that next month's rerun produces the same picture, and never letting a licence you did not read decide whether you can sell the result. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A cutout that keeps the actual product The generated scene changes everything except the item being sold. ### Steps 1. Store the mask as an asset, not as a temporary file Every rerun months later needs exactly this mask. Files: `src/masking.py` 2. Composite the original pixels back at full resolution The generated frame is a background, not a photograph of your goods. Files: `src/composite.py` 3. Measure the difference inside the mask and flag it This is the automated version of a second pair of eyes. Files: `src/composite.py`, `tests/test_mask_diff.py` ### Done when - [ ] Product pixels are unchanged inside the mask - [ ] An over-strong generation is flagged automatically - [ ] Masks are stored and reused on rerun - [ ] Transparent source images are handled ### Watch out - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. ## Phase 2 · Jobs you can actually run again An output records everything needed to reproduce it, and says so when it cannot. ### Steps 1. Store the whole graph, not a summary of it The graph is the recipe and the seed is one ingredient. Files: `src/jobs.py`, `src/backend.py` 2. Hash every model file involved Names are reused; contents are not. Files: `src/registry.py`, `tests/test_rerun.py` 3. Compare the rerun and report the difference An honest mismatch is far more useful than a silent one. Files: `src/rerun.py` ### Done when - [ ] A rerun on an unchanged backend matches - [ ] A swapped model file is detected by hash - [ ] Backend version is recorded per job - [ ] A down backend fails clearly and is resumable ### Watch out - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. ## Phase 3 · The licence gate and the missing-file problem A model cannot run in a project its licence forbids, and a missing file is never a silent substitution. ### Steps 1. Make licence and source required registry fields A blank licence field is how a legal problem is stored. Files: `src/registry.py` 2. Refuse rather than warn on a licence mismatch The warning is read once and dismissed forever. Files: `src/gate.py`, `tests/test_licence_gate.py` 3. Verify referenced models by hash before submitting The backend's helpfulness is the problem here. Files: `src/jobs.py` ### Done when - [ ] A non-commercial model is refused in a commercial project - [ ] A missing model refuses the job before submission - [ ] No registered model has a blank licence - [ ] Memory estimates appear before a run ### Watch out - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. ## Phase 4 · Batches that survive the hardware A run of two hundred variants can fail at sixty and continue from sixty. ### Steps 1. Write the plan before you run anything The plan is what makes resume possible at all. Files: `src/batch.py` 2. Execute serially with a timeout per job One card, one job, and a stall that ends. Files: `src/batch.py`, `tests/test_resume.py` 3. Detect the backend dying and wait for it Out of memory is a normal Tuesday, not an exception. Files: `src/backend.py` ### Done when - [ ] A killed backend resumes without duplicates - [ ] Timeouts fail one variant, not the batch - [ ] The plan exists before execution begins - [ ] Individual variants can be retried ### Watch out - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. ## Not in this build - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. ## After v1, if you want it - A second backend behind the same job interface, once rerun comparison has run for a month - Automatic prop and angle sets per product category, which are batch plans and nothing new - Colour calibration against a reference card in the source photograph, which the mask work already makes possible ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Localhost only. There is no account system and there should not be. APP_PORT=8020 # Required. Your own generation server. The only place jobs are ever submitted. BACKEND_URL=http://127.0.0.1:8188 # Required. Projects, models, jobs, outputs and batch plans. DATABASE_PATH=./data/studio.db # Required. Images and their sidecars, kept together. OUTPUT_DIR=./data/outputs # Required. The disk ceiling. Retention runs against it rather than against hope. OUTPUT_MAX_BYTES=214748364800 # Required. Above this, the product pixels moved and the output is not shippable. MASK_DIFF_THRESHOLD_PCT=0.5 # Required. One variant fails and the batch continues. JOB_TIMEOUT_SECONDS=600 # Required. Favourites are kept. The other thousands are not. RETAIN_NON_FAVOURITE_DAYS=30
You are building a lean indie version of Flair AI. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Flair AI · indie build A local workspace for product scenes that drives your own model server, whose hard problems are keeping the real product pixels untouched, recording enough of each job that next month's rerun produces the same picture, and never letting a licence you did not read decide whether you can sell the result. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local service with a small frontend, bound to localhost, no accounts | this is a workstation tool and the GPU is next to you | | Backend | Your own model server called over its HTTP API, one endpoint in configuration | the generation engine is a dependency, not something to reimplement | | Compositing | The masked product pasted back over the generated scene at full resolution | the scene may be invented and the goods may not | | Provenance | Workflow graph, seed, sampler settings and model file hashes stored with every output | a seed alone does not reproduce anything | | Registry | Every model file recorded with its licence, source and a commercial flag | the licence question arrives when the picture is already on a listing | | Storage | Outputs on local disk with a sidecar per image, a ceiling and a retention rule | a batch run fills a disk faster than anyone expects | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs the service, the job queue and the compositing. - Get it: python.org or your package manager. - Verify: python3 --version prints 3.12 or higher - [ ] **A local generation backend with an HTTP API** · free - Why: This build submits jobs and stores provenance; it does not implement diffusion, and the backend is the one dependency it cannot work without. - Get it: Install ComfyUI or an equivalent server that exposes a job API on your own machine. - Verify: A minimal workflow submitted over HTTP returns an image - [ ] **A machine with enough video memory for your chosen models** · free if you own it, hourly if rented - Why: The memory estimate feature exists because the alternative is discovering the limit during a two hundred image batch. - Get it: A card with the memory your chosen checkpoint needs, or a rented machine you control. - Verify: A single generation at your target resolution completes without swapping - [ ] **Model files whose licences you have read** · free, subject to the licence - Why: The licence determines whether the output can be used commercially, and the terms for some popular weights have been revised more than once. - Get it: Download the weights and save the licence text and URL beside each file. - Verify: Every model in the registry has a licence, a source URL and a commercial flag - [ ] **Your mask threshold, retention and commercial flags** · free - Why: These three decide what is shippable, what is deleted and what can run, and each is enforced in code. - Get it: Write down the permitted mask difference, how long non-favourites are kept, and which projects are commercial. - Verify: All three appear in the configuration and in tests ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. - specialized product-photo workflow, templates, model quality, and hosted rendering - frontier proprietary models - hosted GPU capacity - licensed training data - moderation and fast global delivery If one of those is essential to you, that is the reason to keep paying for Flair AI, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Flair AI The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local product-photography workspace that drives your own model server and records how every image was made to replace Flair AI. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two rules, and both of them decide whether the picture is usable rather than whether it is good. The licence on the weights decides whether you may sell what comes out. FLUX.1 [dev] is under a Non-Commercial License, version 2.0 dated November 2025, whose non-commercial purpose is defined as receiving no direct or indirect payment arising from use of the model, while FLUX.1 [schnell] is released under Apache 2.0 (both checked). So the model registry stores a licence and its source for every file, each project is marked commercial or not, and a non-commercial model cannot run in a commercial project. Read the licence of the exact weights you downloaded, since these terms have been revised more than once. Second, generate the scene, never the product. The surface, the light and the background are yours to invent; the item a customer receives in a box is not, and a redrawn label or an invented logo is a misdescribed good. The real photograph is composited back over the generated scene at full resolution, and any output where the product pixels moved is flagged, not shipped. ### Stack (fixed, do not substitute) - Runtime: A local service with a small frontend, bound to localhost, no accounts · this is a workstation tool and the GPU is next to you - Backend: Your own model server called over its HTTP API, one endpoint in configuration · the generation engine is a dependency, not something to reimplement - Compositing: The masked product pasted back over the generated scene at full resolution · the scene may be invented and the goods may not - Provenance: Workflow graph, seed, sampler settings and model file hashes stored with every output · a seed alone does not reproduce anything - Registry: Every model file recorded with its licence, source and a commercial flag · the licence question arrives when the picture is already on a listing - Storage: Outputs on local disk with a sidecar per image, a ceiling and a retention rule · a batch run fills a disk faster than anyone expects ### Data model (create this before Phase 1) - models(id, filename, sha256, kind, licence, licence_url, commercial_ok, added_at) · kind is checkpoint, lora, vae or upscaler - projects(id, name, commercial, product_asset_id, created_at) · the commercial flag is what the licence gate reads - assets(id, project_id, kind, path, sha256, width, height) · kind is source_photo, mask, prop or reference - jobs(id, project_id, workflow_sha256, workflow_json, seed, sampler, steps, cfg, model_ids_json, backend_version, state, attempts, started_at, finished_at, last_error) - outputs(id, job_id, path, sha256, width, height, mask_diff_pct, sidecar_path, favourite, note) - batches(id, project_id, plan_json, total, completed, failed, created_at) · so a run that dies at image sixty knows which sixty exist - An output is shippable only when every model in its job is permitted for that project's commercial flag and its mask difference is under the stated threshold. ### Phase 1 · A cutout that keeps the actual product Build: Cut the product out to an alpha mask, with manual repair for the edges the automatic pass gets wrong, and store the mask as an asset. Generate the scene, then composite the original product pixels back over the result at full resolution. Measure the difference inside the mask between the source and the final image, and flag anything above a stated threshold. Trap: Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. Done when: The composited output is pixel-identical to the source photograph inside the mask, apart from an explicitly permitted edge blend measured in pixels. A deliberately over-strong generation is flagged by the mask difference check and cannot be marked shippable, tested. Mask edits are stored as an asset so a later rerun uses the same mask. A source photograph with transparency already present is handled rather than silently flattened. Do not build yet: No batches, model registry, contact sheet or export. ### Phase 2 · Jobs you can actually run again Build: Submit to your own model server over its HTTP API and poll for the result. Store the full workflow graph, its hash, the seed, sampler, steps, guidance, the file hash of every model involved and the backend version with the job. Offer a rerun that resubmits exactly that graph, compares the output hash, and reports honestly when the result differs. Trap: Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. Done when: A rerun of a stored job on an unchanged backend reproduces the same output hash, tested. Replacing a model file with different contents under the same name causes the rerun to report a hash mismatch rather than proceeding quietly. The backend version is recorded with every job and shown beside the output. A job submitted while the backend is down fails with a clear reason and can be resubmitted without re-entering anything. Do not build yet: No licence gate, batches or contact sheet yet. ### Phase 3 · The licence gate and the missing-file problem Build: Register every model file with its licence, the URL you read it at, and whether it permits the commercial use you intend. Mark each project commercial or not. Refuse a job whose models are not permitted, with the licence and its URL in the refusal. Verify every model referenced by a workflow exists by hash before submitting, and estimate the memory the run needs so a failure is predicted rather than discovered. Trap: A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. Done when: A job in a commercial project using a model marked non-commercial is refused with the licence name and URL shown, tested. A workflow referencing a missing model is refused before submission rather than running with a substitution, tested by removing a file. Every registered model records a licence and the URL it was read at, with no blank values permitted. The memory estimate appears before a run and a run that exceeds available memory is warned about. Do not build yet: No batches, contact sheet or export yet. ### Phase 4 · Batches that survive the hardware Build: Plan a batch as variants across props, angles, aspect ratios and seeds, writing the plan before anything runs. Execute one job at a time against the backend with a per-job timeout, recording each result as it lands. Detect a backend that has crashed or run out of memory, wait for it to return, and resume from the first incomplete item. Show progress and elapsed cost in time. Trap: Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. Done when: Killing the backend mid-batch and restarting resumes at the first incomplete variant with nothing produced twice, tested. A per-job timeout releases the queue and marks that variant failed rather than stalling the batch. The batch plan is written before execution and shows completed against total throughout. A failed variant can be retried individually without rerunning the batch. Do not build yet: No contact sheet, sidecars or export yet. ### Phase 5 · The contact sheet, the sidecar and the disk Build: A searchable contact sheet with compare, favourite, annotate and rerun, filtered by project, model and licence status. Export an image with a sidecar file recording the workflow hash, seed, models and their licences, plus a note that the scene was generated and the product was not. Set a disk ceiling and a retention rule for non-favourited outputs, and make deleting a project remove its files as well as its rows. Trap: Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together. Done when: Every exported image has a sidecar naming the workflow hash, seed, models and licences, checked by opening one. The contact sheet filters by licence status so non-commercial outputs can be found in one click. Non-favourited outputs past the retention are deleted and the deletion is logged. Deleting a project removes its files and its rows, asserted in a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Training or fine-tuning a frontier model, which is a hardware budget and a dataset problem rather than an application one. - Copying a vendor's proprietary model or its training data, which is both the thing you cannot do and the thing that makes the subscription worth paying. - Hosting generation for other people, which brings moderation, abuse handling and a GPU fleet you would have to keep fed. - Generating the product itself rather than the scene around it, because a redrawn label on an item for sale is a misdescription of goods. ### README must contain - That the licence of each model file is recorded with its source URL, and that a non-commercial model is refused in a commercial project - That the scene is generated and the product is composited back unchanged, with the mask difference measured on every output - That every job stores its workflow graph, seed and model hashes, and that a rerun reports a mismatch rather than hiding it - How a batch resumes after the backend dies, and where the plan is written - Where outputs and sidecars live, what the retention rule deletes, and what deleting a project removes ===== AGENTS.md ===== # Agent instructions · Flair AI indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local service with a small frontend, bound to localhost, no accounts, Your own model server called over its HTTP API, one endpoint in configuration, The masked product pasted back over the generated scene at full resolution, Workflow graph, seed, sampler settings and model file hashes stored with every output, Every model file recorded with its licence, source and a commercial flag, Outputs on local disk with a sidecar per image, a ceiling and a retention rule. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. ===== BUILD_PLAN.md ===== # Build plan · Flair AI A local workspace for product scenes that drives your own model server, whose hard problems are keeping the real product pixels untouched, recording enough of each job that next month's rerun produces the same picture, and never letting a licence you did not read decide whether you can sell the result. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A cutout that keeps the actual product The generated scene changes everything except the item being sold. ### Steps 1. Store the mask as an asset, not as a temporary file Every rerun months later needs exactly this mask. Files: `src/masking.py` 2. Composite the original pixels back at full resolution The generated frame is a background, not a photograph of your goods. Files: `src/composite.py` 3. Measure the difference inside the mask and flag it This is the automated version of a second pair of eyes. Files: `src/composite.py`, `tests/test_mask_diff.py` ### Done when - [ ] Product pixels are unchanged inside the mask - [ ] An over-strong generation is flagged automatically - [ ] Masks are stored and reused on rerun - [ ] Transparent source images are handled ### Watch out - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. ## Phase 2 · Jobs you can actually run again An output records everything needed to reproduce it, and says so when it cannot. ### Steps 1. Store the whole graph, not a summary of it The graph is the recipe and the seed is one ingredient. Files: `src/jobs.py`, `src/backend.py` 2. Hash every model file involved Names are reused; contents are not. Files: `src/registry.py`, `tests/test_rerun.py` 3. Compare the rerun and report the difference An honest mismatch is far more useful than a silent one. Files: `src/rerun.py` ### Done when - [ ] A rerun on an unchanged backend matches - [ ] A swapped model file is detected by hash - [ ] Backend version is recorded per job - [ ] A down backend fails clearly and is resumable ### Watch out - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. ## Phase 3 · The licence gate and the missing-file problem A model cannot run in a project its licence forbids, and a missing file is never a silent substitution. ### Steps 1. Make licence and source required registry fields A blank licence field is how a legal problem is stored. Files: `src/registry.py` 2. Refuse rather than warn on a licence mismatch The warning is read once and dismissed forever. Files: `src/gate.py`, `tests/test_licence_gate.py` 3. Verify referenced models by hash before submitting The backend's helpfulness is the problem here. Files: `src/jobs.py` ### Done when - [ ] A non-commercial model is refused in a commercial project - [ ] A missing model refuses the job before submission - [ ] No registered model has a blank licence - [ ] Memory estimates appear before a run ### Watch out - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. ## Phase 4 · Batches that survive the hardware A run of two hundred variants can fail at sixty and continue from sixty. ### Steps 1. Write the plan before you run anything The plan is what makes resume possible at all. Files: `src/batch.py` 2. Execute serially with a timeout per job One card, one job, and a stall that ends. Files: `src/batch.py`, `tests/test_resume.py` 3. Detect the backend dying and wait for it Out of memory is a normal Tuesday, not an exception. Files: `src/backend.py` ### Done when - [ ] A killed backend resumes without duplicates - [ ] Timeouts fail one variant, not the batch - [ ] The plan exists before execution begins - [ ] Individual variants can be retried ### Watch out - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. ## Not in this build - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. ## After v1, if you want it - A second backend behind the same job interface, once rerun comparison has run for a month - Automatic prop and angle sets per product category, which are batch plans and nothing new - Colour calibration against a reference card in the source photograph, which the mask work already makes possible ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Localhost only. There is no account system and there should not be. APP_PORT=8020 # Required. Your own generation server. The only place jobs are ever submitted. BACKEND_URL=http://127.0.0.1:8188 # Required. Projects, models, jobs, outputs and batch plans. DATABASE_PATH=./data/studio.db # Required. Images and their sidecars, kept together. OUTPUT_DIR=./data/outputs # Required. The disk ceiling. Retention runs against it rather than against hope. OUTPUT_MAX_BYTES=214748364800 # Required. Above this, the product pixels moved and the output is not shippable. MASK_DIFF_THRESHOLD_PCT=0.5 # Required. One variant fails and the batch continues. JOB_TIMEOUT_SECONDS=600 # Required. Favourites are kept. The other thousands are not. RETAIN_NON_FAVOURITE_DAYS=30
You are building a production product version of Flair AI. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Flair AI · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Flair AI, compose product scenes from cutouts, props, and generated backgrounds. The hard boundary is specialized product-photo workflow, templates, model quality, and hosted rendering, plus frontier models, compute, and data. ## Product outcome A studio tool that never redraws the product, can reproduce any image it made or tell you exactly why it cannot, and refuses to run a model whose licence does not permit what the project is for. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - GPU-capable machine or user-supplied generation API - ComfyUI - model files with appropriate licenses - local storage ## Explicit non-goals for v1 - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. - specialized product-photo workflow, templates, model quality, and hosted rendering - frontier proprietary models - hosted GPU capacity - licensed training data - moderation and fast global delivery ## Success criteria - Product pixels are confirmed unchanged inside the mask - A rerun on an unchanged backend is confirmed to match - A non-commercial model is confirmed refused in a commercial project - A killed backend mid-batch is confirmed to resume without duplicates - Every export is confirmed to carry a sidecar with models and licences ===== BRIEF.md ===== # Build brief · Flair AI The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local product-photography workspace that drives your own model server and records how every image was made to replace Flair AI. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two rules, and both of them decide whether the picture is usable rather than whether it is good. The licence on the weights decides whether you may sell what comes out. FLUX.1 [dev] is under a Non-Commercial License, version 2.0 dated November 2025, whose non-commercial purpose is defined as receiving no direct or indirect payment arising from use of the model, while FLUX.1 [schnell] is released under Apache 2.0 (both checked). So the model registry stores a licence and its source for every file, each project is marked commercial or not, and a non-commercial model cannot run in a commercial project. Read the licence of the exact weights you downloaded, since these terms have been revised more than once. Second, generate the scene, never the product. The surface, the light and the background are yours to invent; the item a customer receives in a box is not, and a redrawn label or an invented logo is a misdescribed good. The real photograph is composited back over the generated scene at full resolution, and any output where the product pixels moved is flagged, not shipped. ### Stack (fixed, do not substitute) - Runtime: A local service with a small frontend, bound to localhost, no accounts · this is a workstation tool and the GPU is next to you - Backend: Your own model server called over its HTTP API, one endpoint in configuration · the generation engine is a dependency, not something to reimplement - Compositing: The masked product pasted back over the generated scene at full resolution · the scene may be invented and the goods may not - Provenance: Workflow graph, seed, sampler settings and model file hashes stored with every output · a seed alone does not reproduce anything - Registry: Every model file recorded with its licence, source and a commercial flag · the licence question arrives when the picture is already on a listing - Storage: Outputs on local disk with a sidecar per image, a ceiling and a retention rule · a batch run fills a disk faster than anyone expects ### Data model (create this before Phase 1) - models(id, filename, sha256, kind, licence, licence_url, commercial_ok, added_at) · kind is checkpoint, lora, vae or upscaler - projects(id, name, commercial, product_asset_id, created_at) · the commercial flag is what the licence gate reads - assets(id, project_id, kind, path, sha256, width, height) · kind is source_photo, mask, prop or reference - jobs(id, project_id, workflow_sha256, workflow_json, seed, sampler, steps, cfg, model_ids_json, backend_version, state, attempts, started_at, finished_at, last_error) - outputs(id, job_id, path, sha256, width, height, mask_diff_pct, sidecar_path, favourite, note) - batches(id, project_id, plan_json, total, completed, failed, created_at) · so a run that dies at image sixty knows which sixty exist - An output is shippable only when every model in its job is permitted for that project's commercial flag and its mask difference is under the stated threshold. ### Phase 1 · A cutout that keeps the actual product Build: Cut the product out to an alpha mask, with manual repair for the edges the automatic pass gets wrong, and store the mask as an asset. Generate the scene, then composite the original product pixels back over the result at full resolution. Measure the difference inside the mask between the source and the final image, and flag anything above a stated threshold. Trap: Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. Done when: The composited output is pixel-identical to the source photograph inside the mask, apart from an explicitly permitted edge blend measured in pixels. A deliberately over-strong generation is flagged by the mask difference check and cannot be marked shippable, tested. Mask edits are stored as an asset so a later rerun uses the same mask. A source photograph with transparency already present is handled rather than silently flattened. Do not build yet: No batches, model registry, contact sheet or export. ### Phase 2 · Jobs you can actually run again Build: Submit to your own model server over its HTTP API and poll for the result. Store the full workflow graph, its hash, the seed, sampler, steps, guidance, the file hash of every model involved and the backend version with the job. Offer a rerun that resubmits exactly that graph, compares the output hash, and reports honestly when the result differs. Trap: Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. Done when: A rerun of a stored job on an unchanged backend reproduces the same output hash, tested. Replacing a model file with different contents under the same name causes the rerun to report a hash mismatch rather than proceeding quietly. The backend version is recorded with every job and shown beside the output. A job submitted while the backend is down fails with a clear reason and can be resubmitted without re-entering anything. Do not build yet: No licence gate, batches or contact sheet yet. ### Phase 3 · The licence gate and the missing-file problem Build: Register every model file with its licence, the URL you read it at, and whether it permits the commercial use you intend. Mark each project commercial or not. Refuse a job whose models are not permitted, with the licence and its URL in the refusal. Verify every model referenced by a workflow exists by hash before submitting, and estimate the memory the run needs so a failure is predicted rather than discovered. Trap: A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. Done when: A job in a commercial project using a model marked non-commercial is refused with the licence name and URL shown, tested. A workflow referencing a missing model is refused before submission rather than running with a substitution, tested by removing a file. Every registered model records a licence and the URL it was read at, with no blank values permitted. The memory estimate appears before a run and a run that exceeds available memory is warned about. Do not build yet: No batches, contact sheet or export yet. ### Phase 4 · Batches that survive the hardware Build: Plan a batch as variants across props, angles, aspect ratios and seeds, writing the plan before anything runs. Execute one job at a time against the backend with a per-job timeout, recording each result as it lands. Detect a backend that has crashed or run out of memory, wait for it to return, and resume from the first incomplete item. Show progress and elapsed cost in time. Trap: Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. Done when: Killing the backend mid-batch and restarting resumes at the first incomplete variant with nothing produced twice, tested. A per-job timeout releases the queue and marks that variant failed rather than stalling the batch. The batch plan is written before execution and shows completed against total throughout. A failed variant can be retried individually without rerunning the batch. Do not build yet: No contact sheet, sidecars or export yet. ### Phase 5 · The contact sheet, the sidecar and the disk Build: A searchable contact sheet with compare, favourite, annotate and rerun, filtered by project, model and licence status. Export an image with a sidecar file recording the workflow hash, seed, models and their licences, plus a note that the scene was generated and the product was not. Set a disk ceiling and a retention rule for non-favourited outputs, and make deleting a project remove its files as well as its rows. Trap: Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together. Done when: Every exported image has a sidecar naming the workflow hash, seed, models and licences, checked by opening one. The contact sheet filters by licence status so non-commercial outputs can be found in one click. Non-favourited outputs past the retention are deleted and the deletion is logged. Deleting a project removes its files and its rows, asserted in a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Training or fine-tuning a frontier model, which is a hardware budget and a dataset problem rather than an application one. - Copying a vendor's proprietary model or its training data, which is both the thing you cannot do and the thing that makes the subscription worth paying. - Hosting generation for other people, which brings moderation, abuse handling and a GPU fleet you would have to keep fed. - Generating the product itself rather than the scene around it, because a redrawn label on an item for sale is a misdescription of goods. ### README must contain - That the licence of each model file is recorded with its source URL, and that a non-commercial model is refused in a commercial project - That the scene is generated and the product is composited back unchanged, with the mask difference measured on every output - That every job stores its workflow graph, seed and model hashes, and that a rerun reports a mismatch rather than hiding it - How a batch resumes after the backend dies, and where the plan is written - Where outputs and sidecars live, what the retention rule deletes, and what deleting a project removes ===== ARCHITECTURE.md ===== # Architecture · Flair AI ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local service with a small frontend, bound to localhost, no accounts | this is a workstation tool and the GPU is next to you | | Backend | Your own model server called over its HTTP API, one endpoint in configuration | the generation engine is a dependency, not something to reimplement | | Compositing | The masked product pasted back over the generated scene at full resolution | the scene may be invented and the goods may not | | Provenance | Workflow graph, seed, sampler settings and model file hashes stored with every output | a seed alone does not reproduce anything | | Registry | Every model file recorded with its licence, source and a commercial flag | the licence question arrives when the picture is already on a listing | | Storage | Outputs on local disk with a sidecar per image, a ceiling and a retention rule | a batch run fills a disk faster than anyone expects | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Masking | Cutouts, stored masks, edge repair. | Any segmentation model, provided the mask is stored as an asset. | | Compositing | Full-resolution paste-back and the difference measurement. | Not swappable for whole-frame regeneration, which is the thing this build refuses. | | Jobs | Graph, seed, hashes, backend version, rerun comparison. | Any backend with a job API, provided the graph and hashes are still stored. | | Registry | Model licences, sources and the commercial gate. | Any store, provided a blank licence remains impossible. | | Batch | Plans, serial execution, resume after a crash. | Any executor, provided resume stays the default path. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `APP_PORT` · required · Localhost only. There is no account system and there should not be. - `BACKEND_URL` · required · Your own generation server. The only place jobs are ever submitted. - `DATABASE_PATH` · required · Projects, models, jobs, outputs and batch plans. - `OUTPUT_DIR` · required · Images and their sidecars, kept together. - `OUTPUT_MAX_BYTES` · required · The disk ceiling. Retention runs against it rather than against hope. - `MASK_DIFF_THRESHOLD_PCT` · required · Above this, the product pixels moved and the output is not shippable. - `JOB_TIMEOUT_SECONDS` · required · One variant fails and the batch continues. - `RETAIN_NON_FAVOURITE_DAYS` · required · Favourites are kept. The other thousands are not. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Flair AI product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local service with a small frontend, bound to localhost, no accounts, Your own model server called over its HTTP API, one endpoint in configuration, The masked product pasted back over the generated scene at full resolution, Workflow graph, seed, sampler settings and model file hashes stored with every output, Every model file recorded with its licence, source and a commercial flag, Outputs on local disk with a sidecar per image, a ceiling and a retention rule. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. - Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together. ===== MILESTONES.md ===== # Delivery milestones · Flair AI Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A cutout that keeps the actual product The generated scene changes everything except the item being sold. ### Steps 1. Store the mask as an asset, not as a temporary file Every rerun months later needs exactly this mask. Files: `src/masking.py` 2. Composite the original pixels back at full resolution The generated frame is a background, not a photograph of your goods. Files: `src/composite.py` 3. Measure the difference inside the mask and flag it This is the automated version of a second pair of eyes. Files: `src/composite.py`, `tests/test_mask_diff.py` ### Done when - [ ] Product pixels are unchanged inside the mask - [ ] An over-strong generation is flagged automatically - [ ] Masks are stored and reused on rerun - [ ] Transparent source images are handled ### Watch out - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. ## M2 · Jobs you can actually run again An output records everything needed to reproduce it, and says so when it cannot. ### Steps 1. Store the whole graph, not a summary of it The graph is the recipe and the seed is one ingredient. Files: `src/jobs.py`, `src/backend.py` 2. Hash every model file involved Names are reused; contents are not. Files: `src/registry.py`, `tests/test_rerun.py` 3. Compare the rerun and report the difference An honest mismatch is far more useful than a silent one. Files: `src/rerun.py` ### Done when - [ ] A rerun on an unchanged backend matches - [ ] A swapped model file is detected by hash - [ ] Backend version is recorded per job - [ ] A down backend fails clearly and is resumable ### Watch out - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. ## M3 · The licence gate and the missing-file problem A model cannot run in a project its licence forbids, and a missing file is never a silent substitution. ### Steps 1. Make licence and source required registry fields A blank licence field is how a legal problem is stored. Files: `src/registry.py` 2. Refuse rather than warn on a licence mismatch The warning is read once and dismissed forever. Files: `src/gate.py`, `tests/test_licence_gate.py` 3. Verify referenced models by hash before submitting The backend's helpfulness is the problem here. Files: `src/jobs.py` ### Done when - [ ] A non-commercial model is refused in a commercial project - [ ] A missing model refuses the job before submission - [ ] No registered model has a blank licence - [ ] Memory estimates appear before a run ### Watch out - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. ## M4 · Batches that survive the hardware A run of two hundred variants can fail at sixty and continue from sixty. ### Steps 1. Write the plan before you run anything The plan is what makes resume possible at all. Files: `src/batch.py` 2. Execute serially with a timeout per job One card, one job, and a stall that ends. Files: `src/batch.py`, `tests/test_resume.py` 3. Detect the backend dying and wait for it Out of memory is a normal Tuesday, not an exception. Files: `src/backend.py` ### Done when - [ ] A killed backend resumes without duplicates - [ ] Timeouts fail one variant, not the batch - [ ] The plan exists before execution begins - [ ] Individual variants can be retried ### Watch out - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. ## M5 · The contact sheet, the sidecar and the disk (production only) Choosing an image is fast, every exported file carries how it was made, and the disk has a ceiling. ### Steps 1. Filter the contact sheet by licence status Then you can answer the listing question in one click. Files: `src/views/sheet.tsx` 2. Write a sidecar beside every export The image and its provenance travel together or not at all. Files: `src/export.py`, `tests/test_sidecar.py` 3. Cap the disk and retain favourites only A batch run is thousands of files you will never open again. Files: `src/retention.py` ### Done when - [ ] Every export has a sidecar with models and licences - [ ] The sheet filters by licence status - [ ] Retention deletes non-favourites and logs it - [ ] Deleting a project removes files and rows ### Watch out - Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together. ===== OPERATIONS.md ===== # Operations · Flair AI ## Backup The database and the sidecars matter more than the images, because an image with its provenance can be regenerated and an image without it cannot. Back up source photographs and masks with the same care as the database; they are the only irreplaceable files here. ## Restore Restore the database, the sources and the masks, then confirm a stored job reruns to the same hash on the restored backend. A mismatch tells you the backend changed, which is worth knowing before a client asks for the same shot at another size. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Backend availability, jobs stuck in progress, batch completion against plan, outputs failing the mask difference check, and disk against the ceiling. ## Incident checklist A batch of images in the wrong style almost always means a referenced model was missing and the backend improvised, which the pre-submission hash check is there to prevent. An output that fails the mask check means the generation strength is too high for that mask. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Product pixels are confirmed unchanged inside the mask - [ ] A rerun on an unchanged backend is confirmed to match - [ ] A non-commercial model is confirmed refused in a commercial project - [ ] A killed backend mid-batch is confirmed to resume without duplicates - [ ] Every export is confirmed to carry a sidecar with models and licences ## Launch constraint Do not market omitted Flair AI capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Localhost only. There is no account system and there should not be. APP_PORT=8020 # Required. Your own generation server. The only place jobs are ever submitted. BACKEND_URL=http://127.0.0.1:8188 # Required. Projects, models, jobs, outputs and batch plans. DATABASE_PATH=./data/studio.db # Required. Images and their sidecars, kept together. OUTPUT_DIR=./data/outputs # Required. The disk ceiling. Retention runs against it rather than against hope. OUTPUT_MAX_BYTES=214748364800 # Required. Above this, the product pixels moved and the output is not shippable. MASK_DIFF_THRESHOLD_PCT=0.5 # Required. One variant fails and the batch continues. JOB_TIMEOUT_SECONDS=600 # Required. Favourites are kept. The other thousands are not. RETAIN_NON_FAVOURITE_DAYS=30
# Flair AI · indie build A local workspace for product scenes that drives your own model server, whose hard problems are keeping the real product pixels untouched, recording enough of each job that next month's rerun produces the same picture, and never letting a licence you did not read decide whether you can sell the result. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local service with a small frontend, bound to localhost, no accounts | this is a workstation tool and the GPU is next to you | | Backend | Your own model server called over its HTTP API, one endpoint in configuration | the generation engine is a dependency, not something to reimplement | | Compositing | The masked product pasted back over the generated scene at full resolution | the scene may be invented and the goods may not | | Provenance | Workflow graph, seed, sampler settings and model file hashes stored with every output | a seed alone does not reproduce anything | | Registry | Every model file recorded with its licence, source and a commercial flag | the licence question arrives when the picture is already on a listing | | Storage | Outputs on local disk with a sidecar per image, a ceiling and a retention rule | a batch run fills a disk faster than anyone expects | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs the service, the job queue and the compositing. - Get it: python.org or your package manager. - Verify: python3 --version prints 3.12 or higher - [ ] **A local generation backend with an HTTP API** · free - Why: This build submits jobs and stores provenance; it does not implement diffusion, and the backend is the one dependency it cannot work without. - Get it: Install ComfyUI or an equivalent server that exposes a job API on your own machine. - Verify: A minimal workflow submitted over HTTP returns an image - [ ] **A machine with enough video memory for your chosen models** · free if you own it, hourly if rented - Why: The memory estimate feature exists because the alternative is discovering the limit during a two hundred image batch. - Get it: A card with the memory your chosen checkpoint needs, or a rented machine you control. - Verify: A single generation at your target resolution completes without swapping - [ ] **Model files whose licences you have read** · free, subject to the licence - Why: The licence determines whether the output can be used commercially, and the terms for some popular weights have been revised more than once. - Get it: Download the weights and save the licence text and URL beside each file. - Verify: Every model in the registry has a licence, a source URL and a commercial flag - [ ] **Your mask threshold, retention and commercial flags** · free - Why: These three decide what is shippable, what is deleted and what can run, and each is enforced in code. - Get it: Write down the permitted mask difference, how long non-favourites are kept, and which projects are commercial. - Verify: All three appear in the configuration and in tests ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. - specialized product-photo workflow, templates, model quality, and hosted rendering - frontier proprietary models - hosted GPU capacity - licensed training data - moderation and fast global delivery If one of those is essential to you, that is the reason to keep paying for Flair AI, and the README should say so rather than pretend.
# Build brief · Flair AI The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local product-photography workspace that drives your own model server and records how every image was made to replace Flair AI. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two rules, and both of them decide whether the picture is usable rather than whether it is good. The licence on the weights decides whether you may sell what comes out. FLUX.1 [dev] is under a Non-Commercial License, version 2.0 dated November 2025, whose non-commercial purpose is defined as receiving no direct or indirect payment arising from use of the model, while FLUX.1 [schnell] is released under Apache 2.0 (both checked). So the model registry stores a licence and its source for every file, each project is marked commercial or not, and a non-commercial model cannot run in a commercial project. Read the licence of the exact weights you downloaded, since these terms have been revised more than once. Second, generate the scene, never the product. The surface, the light and the background are yours to invent; the item a customer receives in a box is not, and a redrawn label or an invented logo is a misdescribed good. The real photograph is composited back over the generated scene at full resolution, and any output where the product pixels moved is flagged, not shipped. ### Stack (fixed, do not substitute) - Runtime: A local service with a small frontend, bound to localhost, no accounts · this is a workstation tool and the GPU is next to you - Backend: Your own model server called over its HTTP API, one endpoint in configuration · the generation engine is a dependency, not something to reimplement - Compositing: The masked product pasted back over the generated scene at full resolution · the scene may be invented and the goods may not - Provenance: Workflow graph, seed, sampler settings and model file hashes stored with every output · a seed alone does not reproduce anything - Registry: Every model file recorded with its licence, source and a commercial flag · the licence question arrives when the picture is already on a listing - Storage: Outputs on local disk with a sidecar per image, a ceiling and a retention rule · a batch run fills a disk faster than anyone expects ### Data model (create this before Phase 1) - models(id, filename, sha256, kind, licence, licence_url, commercial_ok, added_at) · kind is checkpoint, lora, vae or upscaler - projects(id, name, commercial, product_asset_id, created_at) · the commercial flag is what the licence gate reads - assets(id, project_id, kind, path, sha256, width, height) · kind is source_photo, mask, prop or reference - jobs(id, project_id, workflow_sha256, workflow_json, seed, sampler, steps, cfg, model_ids_json, backend_version, state, attempts, started_at, finished_at, last_error) - outputs(id, job_id, path, sha256, width, height, mask_diff_pct, sidecar_path, favourite, note) - batches(id, project_id, plan_json, total, completed, failed, created_at) · so a run that dies at image sixty knows which sixty exist - An output is shippable only when every model in its job is permitted for that project's commercial flag and its mask difference is under the stated threshold. ### Phase 1 · A cutout that keeps the actual product Build: Cut the product out to an alpha mask, with manual repair for the edges the automatic pass gets wrong, and store the mask as an asset. Generate the scene, then composite the original product pixels back over the result at full resolution. Measure the difference inside the mask between the source and the final image, and flag anything above a stated threshold. Trap: Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. Done when: The composited output is pixel-identical to the source photograph inside the mask, apart from an explicitly permitted edge blend measured in pixels. A deliberately over-strong generation is flagged by the mask difference check and cannot be marked shippable, tested. Mask edits are stored as an asset so a later rerun uses the same mask. A source photograph with transparency already present is handled rather than silently flattened. Do not build yet: No batches, model registry, contact sheet or export. ### Phase 2 · Jobs you can actually run again Build: Submit to your own model server over its HTTP API and poll for the result. Store the full workflow graph, its hash, the seed, sampler, steps, guidance, the file hash of every model involved and the backend version with the job. Offer a rerun that resubmits exactly that graph, compares the output hash, and reports honestly when the result differs. Trap: Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. Done when: A rerun of a stored job on an unchanged backend reproduces the same output hash, tested. Replacing a model file with different contents under the same name causes the rerun to report a hash mismatch rather than proceeding quietly. The backend version is recorded with every job and shown beside the output. A job submitted while the backend is down fails with a clear reason and can be resubmitted without re-entering anything. Do not build yet: No licence gate, batches or contact sheet yet. ### Phase 3 · The licence gate and the missing-file problem Build: Register every model file with its licence, the URL you read it at, and whether it permits the commercial use you intend. Mark each project commercial or not. Refuse a job whose models are not permitted, with the licence and its URL in the refusal. Verify every model referenced by a workflow exists by hash before submitting, and estimate the memory the run needs so a failure is predicted rather than discovered. Trap: A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. Done when: A job in a commercial project using a model marked non-commercial is refused with the licence name and URL shown, tested. A workflow referencing a missing model is refused before submission rather than running with a substitution, tested by removing a file. Every registered model records a licence and the URL it was read at, with no blank values permitted. The memory estimate appears before a run and a run that exceeds available memory is warned about. Do not build yet: No batches, contact sheet or export yet. ### Phase 4 · Batches that survive the hardware Build: Plan a batch as variants across props, angles, aspect ratios and seeds, writing the plan before anything runs. Execute one job at a time against the backend with a per-job timeout, recording each result as it lands. Detect a backend that has crashed or run out of memory, wait for it to return, and resume from the first incomplete item. Show progress and elapsed cost in time. Trap: Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. Done when: Killing the backend mid-batch and restarting resumes at the first incomplete variant with nothing produced twice, tested. A per-job timeout releases the queue and marks that variant failed rather than stalling the batch. The batch plan is written before execution and shows completed against total throughout. A failed variant can be retried individually without rerunning the batch. Do not build yet: No contact sheet, sidecars or export yet. ### Phase 5 · The contact sheet, the sidecar and the disk Build: A searchable contact sheet with compare, favourite, annotate and rerun, filtered by project, model and licence status. Export an image with a sidecar file recording the workflow hash, seed, models and their licences, plus a note that the scene was generated and the product was not. Set a disk ceiling and a retention rule for non-favourited outputs, and make deleting a project remove its files as well as its rows. Trap: Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together. Done when: Every exported image has a sidecar naming the workflow hash, seed, models and licences, checked by opening one. The contact sheet filters by licence status so non-commercial outputs can be found in one click. Non-favourited outputs past the retention are deleted and the deletion is logged. Deleting a project removes its files and its rows, asserted in a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Training or fine-tuning a frontier model, which is a hardware budget and a dataset problem rather than an application one. - Copying a vendor's proprietary model or its training data, which is both the thing you cannot do and the thing that makes the subscription worth paying. - Hosting generation for other people, which brings moderation, abuse handling and a GPU fleet you would have to keep fed. - Generating the product itself rather than the scene around it, because a redrawn label on an item for sale is a misdescription of goods. ### README must contain - That the licence of each model file is recorded with its source URL, and that a non-commercial model is refused in a commercial project - That the scene is generated and the product is composited back unchanged, with the mask difference measured on every output - That every job stores its workflow graph, seed and model hashes, and that a rerun reports a mismatch rather than hiding it - How a batch resumes after the backend dies, and where the plan is written - Where outputs and sidecars live, what the retention rule deletes, and what deleting a project removes
# Agent instructions · Flair AI indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local service with a small frontend, bound to localhost, no accounts, Your own model server called over its HTTP API, one endpoint in configuration, The masked product pasted back over the generated scene at full resolution, Workflow graph, seed, sampler settings and model file hashes stored with every output, Every model file recorded with its licence, source and a commercial flag, Outputs on local disk with a sidecar per image, a ceiling and a retention rule. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default.
# Build plan · Flair AI A local workspace for product scenes that drives your own model server, whose hard problems are keeping the real product pixels untouched, recording enough of each job that next month's rerun produces the same picture, and never letting a licence you did not read decide whether you can sell the result. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A cutout that keeps the actual product The generated scene changes everything except the item being sold. ### Steps 1. Store the mask as an asset, not as a temporary file Every rerun months later needs exactly this mask. Files: `src/masking.py` 2. Composite the original pixels back at full resolution The generated frame is a background, not a photograph of your goods. Files: `src/composite.py` 3. Measure the difference inside the mask and flag it This is the automated version of a second pair of eyes. Files: `src/composite.py`, `tests/test_mask_diff.py` ### Done when - [ ] Product pixels are unchanged inside the mask - [ ] An over-strong generation is flagged automatically - [ ] Masks are stored and reused on rerun - [ ] Transparent source images are handled ### Watch out - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. ## Phase 2 · Jobs you can actually run again An output records everything needed to reproduce it, and says so when it cannot. ### Steps 1. Store the whole graph, not a summary of it The graph is the recipe and the seed is one ingredient. Files: `src/jobs.py`, `src/backend.py` 2. Hash every model file involved Names are reused; contents are not. Files: `src/registry.py`, `tests/test_rerun.py` 3. Compare the rerun and report the difference An honest mismatch is far more useful than a silent one. Files: `src/rerun.py` ### Done when - [ ] A rerun on an unchanged backend matches - [ ] A swapped model file is detected by hash - [ ] Backend version is recorded per job - [ ] A down backend fails clearly and is resumable ### Watch out - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. ## Phase 3 · The licence gate and the missing-file problem A model cannot run in a project its licence forbids, and a missing file is never a silent substitution. ### Steps 1. Make licence and source required registry fields A blank licence field is how a legal problem is stored. Files: `src/registry.py` 2. Refuse rather than warn on a licence mismatch The warning is read once and dismissed forever. Files: `src/gate.py`, `tests/test_licence_gate.py` 3. Verify referenced models by hash before submitting The backend's helpfulness is the problem here. Files: `src/jobs.py` ### Done when - [ ] A non-commercial model is refused in a commercial project - [ ] A missing model refuses the job before submission - [ ] No registered model has a blank licence - [ ] Memory estimates appear before a run ### Watch out - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. ## Phase 4 · Batches that survive the hardware A run of two hundred variants can fail at sixty and continue from sixty. ### Steps 1. Write the plan before you run anything The plan is what makes resume possible at all. Files: `src/batch.py` 2. Execute serially with a timeout per job One card, one job, and a stall that ends. Files: `src/batch.py`, `tests/test_resume.py` 3. Detect the backend dying and wait for it Out of memory is a normal Tuesday, not an exception. Files: `src/backend.py` ### Done when - [ ] A killed backend resumes without duplicates - [ ] Timeouts fail one variant, not the batch - [ ] The plan exists before execution begins - [ ] Individual variants can be retried ### Watch out - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. ## Not in this build - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. ## After v1, if you want it - A second backend behind the same job interface, once rerun comparison has run for a month - Automatic prop and angle sets per product category, which are batch plans and nothing new - Colour calibration against a reference card in the source photograph, which the mask work already makes possible
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Localhost only. There is no account system and there should not be. APP_PORT=8020 # Required. Your own generation server. The only place jobs are ever submitted. BACKEND_URL=http://127.0.0.1:8188 # Required. Projects, models, jobs, outputs and batch plans. DATABASE_PATH=./data/studio.db # Required. Images and their sidecars, kept together. OUTPUT_DIR=./data/outputs # Required. The disk ceiling. Retention runs against it rather than against hope. OUTPUT_MAX_BYTES=214748364800 # Required. Above this, the product pixels moved and the output is not shippable. MASK_DIFF_THRESHOLD_PCT=0.5 # Required. One variant fails and the batch continues. JOB_TIMEOUT_SECONDS=600 # Required. Favourites are kept. The other thousands are not. RETAIN_NON_FAVOURITE_DAYS=30
# Flair AI · product brief ## Problem The core loop is buildable, but a dependable replacement becomes a real weekend or multi-day project. For Flair AI, compose product scenes from cutouts, props, and generated backgrounds. The hard boundary is specialized product-photo workflow, templates, model quality, and hosted rendering, plus frontier models, compute, and data. ## Product outcome A studio tool that never redraws the product, can reproduce any image it made or tell you exactly why it cannot, and refuses to run a model whose licence does not permit what the project is for. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - GPU-capable machine or user-supplied generation API - ComfyUI - model files with appropriate licenses - local storage ## Explicit non-goals for v1 - Training or fine-tuning a model. - Any vendor's proprietary model or dataset. - Hosted generation for other people. - Generating the product rather than the scene. - specialized product-photo workflow, templates, model quality, and hosted rendering - frontier proprietary models - hosted GPU capacity - licensed training data - moderation and fast global delivery ## Success criteria - Product pixels are confirmed unchanged inside the mask - A rerun on an unchanged backend is confirmed to match - A non-commercial model is confirmed refused in a commercial project - A killed backend mid-batch is confirmed to resume without duplicates - Every export is confirmed to carry a sidecar with models and licences
# Build brief · Flair AI The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local product-photography workspace that drives your own model server and records how every image was made to replace Flair AI. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Two rules, and both of them decide whether the picture is usable rather than whether it is good. The licence on the weights decides whether you may sell what comes out. FLUX.1 [dev] is under a Non-Commercial License, version 2.0 dated November 2025, whose non-commercial purpose is defined as receiving no direct or indirect payment arising from use of the model, while FLUX.1 [schnell] is released under Apache 2.0 (both checked). So the model registry stores a licence and its source for every file, each project is marked commercial or not, and a non-commercial model cannot run in a commercial project. Read the licence of the exact weights you downloaded, since these terms have been revised more than once. Second, generate the scene, never the product. The surface, the light and the background are yours to invent; the item a customer receives in a box is not, and a redrawn label or an invented logo is a misdescribed good. The real photograph is composited back over the generated scene at full resolution, and any output where the product pixels moved is flagged, not shipped. ### Stack (fixed, do not substitute) - Runtime: A local service with a small frontend, bound to localhost, no accounts · this is a workstation tool and the GPU is next to you - Backend: Your own model server called over its HTTP API, one endpoint in configuration · the generation engine is a dependency, not something to reimplement - Compositing: The masked product pasted back over the generated scene at full resolution · the scene may be invented and the goods may not - Provenance: Workflow graph, seed, sampler settings and model file hashes stored with every output · a seed alone does not reproduce anything - Registry: Every model file recorded with its licence, source and a commercial flag · the licence question arrives when the picture is already on a listing - Storage: Outputs on local disk with a sidecar per image, a ceiling and a retention rule · a batch run fills a disk faster than anyone expects ### Data model (create this before Phase 1) - models(id, filename, sha256, kind, licence, licence_url, commercial_ok, added_at) · kind is checkpoint, lora, vae or upscaler - projects(id, name, commercial, product_asset_id, created_at) · the commercial flag is what the licence gate reads - assets(id, project_id, kind, path, sha256, width, height) · kind is source_photo, mask, prop or reference - jobs(id, project_id, workflow_sha256, workflow_json, seed, sampler, steps, cfg, model_ids_json, backend_version, state, attempts, started_at, finished_at, last_error) - outputs(id, job_id, path, sha256, width, height, mask_diff_pct, sidecar_path, favourite, note) - batches(id, project_id, plan_json, total, completed, failed, created_at) · so a run that dies at image sixty knows which sixty exist - An output is shippable only when every model in its job is permitted for that project's commercial flag and its mask difference is under the stated threshold. ### Phase 1 · A cutout that keeps the actual product Build: Cut the product out to an alpha mask, with manual repair for the edges the automatic pass gets wrong, and store the mask as an asset. Generate the scene, then composite the original product pixels back over the result at full resolution. Measure the difference inside the mask between the source and the final image, and flag anything above a stated threshold. Trap: Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. Done when: The composited output is pixel-identical to the source photograph inside the mask, apart from an explicitly permitted edge blend measured in pixels. A deliberately over-strong generation is flagged by the mask difference check and cannot be marked shippable, tested. Mask edits are stored as an asset so a later rerun uses the same mask. A source photograph with transparency already present is handled rather than silently flattened. Do not build yet: No batches, model registry, contact sheet or export. ### Phase 2 · Jobs you can actually run again Build: Submit to your own model server over its HTTP API and poll for the result. Store the full workflow graph, its hash, the seed, sampler, steps, guidance, the file hash of every model involved and the backend version with the job. Offer a rerun that resubmits exactly that graph, compares the output hash, and reports honestly when the result differs. Trap: Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. Done when: A rerun of a stored job on an unchanged backend reproduces the same output hash, tested. Replacing a model file with different contents under the same name causes the rerun to report a hash mismatch rather than proceeding quietly. The backend version is recorded with every job and shown beside the output. A job submitted while the backend is down fails with a clear reason and can be resubmitted without re-entering anything. Do not build yet: No licence gate, batches or contact sheet yet. ### Phase 3 · The licence gate and the missing-file problem Build: Register every model file with its licence, the URL you read it at, and whether it permits the commercial use you intend. Mark each project commercial or not. Refuse a job whose models are not permitted, with the licence and its URL in the refusal. Verify every model referenced by a workflow exists by hash before submitting, and estimate the memory the run needs so a failure is predicted rather than discovered. Trap: A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. Done when: A job in a commercial project using a model marked non-commercial is refused with the licence name and URL shown, tested. A workflow referencing a missing model is refused before submission rather than running with a substitution, tested by removing a file. Every registered model records a licence and the URL it was read at, with no blank values permitted. The memory estimate appears before a run and a run that exceeds available memory is warned about. Do not build yet: No batches, contact sheet or export yet. ### Phase 4 · Batches that survive the hardware Build: Plan a batch as variants across props, angles, aspect ratios and seeds, writing the plan before anything runs. Execute one job at a time against the backend with a per-job timeout, recording each result as it lands. Detect a backend that has crashed or run out of memory, wait for it to return, and resume from the first incomplete item. Show progress and elapsed cost in time. Trap: Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. Done when: Killing the backend mid-batch and restarting resumes at the first incomplete variant with nothing produced twice, tested. A per-job timeout releases the queue and marks that variant failed rather than stalling the batch. The batch plan is written before execution and shows completed against total throughout. A failed variant can be retried individually without rerunning the batch. Do not build yet: No contact sheet, sidecars or export yet. ### Phase 5 · The contact sheet, the sidecar and the disk Build: A searchable contact sheet with compare, favourite, annotate and rerun, filtered by project, model and licence status. Export an image with a sidecar file recording the workflow hash, seed, models and their licences, plus a note that the scene was generated and the product was not. Set a disk ceiling and a retention rule for non-favourited outputs, and make deleting a project remove its files as well as its rows. Trap: Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together. Done when: Every exported image has a sidecar naming the workflow hash, seed, models and licences, checked by opening one. The contact sheet filters by licence status so non-commercial outputs can be found in one click. Non-favourited outputs past the retention are deleted and the deletion is logged. Deleting a project removes its files and its rows, asserted in a test. Do not build yet: Nothing after this. ### Out of scope (and why) - Training or fine-tuning a frontier model, which is a hardware budget and a dataset problem rather than an application one. - Copying a vendor's proprietary model or its training data, which is both the thing you cannot do and the thing that makes the subscription worth paying. - Hosting generation for other people, which brings moderation, abuse handling and a GPU fleet you would have to keep fed. - Generating the product itself rather than the scene around it, because a redrawn label on an item for sale is a misdescription of goods. ### README must contain - That the licence of each model file is recorded with its source URL, and that a non-commercial model is refused in a commercial project - That the scene is generated and the product is composited back unchanged, with the mask difference measured on every output - That every job stores its workflow graph, seed and model hashes, and that a rerun reports a mismatch rather than hiding it - How a batch resumes after the backend dies, and where the plan is written - Where outputs and sidecars live, what the retention rule deletes, and what deleting a project removes
# Architecture · Flair AI ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A local service with a small frontend, bound to localhost, no accounts | this is a workstation tool and the GPU is next to you | | Backend | Your own model server called over its HTTP API, one endpoint in configuration | the generation engine is a dependency, not something to reimplement | | Compositing | The masked product pasted back over the generated scene at full resolution | the scene may be invented and the goods may not | | Provenance | Workflow graph, seed, sampler settings and model file hashes stored with every output | a seed alone does not reproduce anything | | Registry | Every model file recorded with its licence, source and a commercial flag | the licence question arrives when the picture is already on a listing | | Storage | Outputs on local disk with a sidecar per image, a ceiling and a retention rule | a batch run fills a disk faster than anyone expects | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Masking | Cutouts, stored masks, edge repair. | Any segmentation model, provided the mask is stored as an asset. | | Compositing | Full-resolution paste-back and the difference measurement. | Not swappable for whole-frame regeneration, which is the thing this build refuses. | | Jobs | Graph, seed, hashes, backend version, rerun comparison. | Any backend with a job API, provided the graph and hashes are still stored. | | Registry | Model licences, sources and the commercial gate. | Any store, provided a blank licence remains impossible. | | Batch | Plans, serial execution, resume after a crash. | Any executor, provided resume stays the default path. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `APP_PORT` · required · Localhost only. There is no account system and there should not be. - `BACKEND_URL` · required · Your own generation server. The only place jobs are ever submitted. - `DATABASE_PATH` · required · Projects, models, jobs, outputs and batch plans. - `OUTPUT_DIR` · required · Images and their sidecars, kept together. - `OUTPUT_MAX_BYTES` · required · The disk ceiling. Retention runs against it rather than against hope. - `MASK_DIFF_THRESHOLD_PCT` · required · Above this, the product pixels moved and the output is not shippable. - `JOB_TIMEOUT_SECONDS` · required · One variant fails and the batch continues. - `RETAIN_NON_FAVOURITE_DAYS` · required · Favourites are kept. The other thousands are not. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Flair AI product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local service with a small frontend, bound to localhost, no accounts, Your own model server called over its HTTP API, one endpoint in configuration, The masked product pasted back over the generated scene at full resolution, Workflow graph, seed, sampler settings and model file hashes stored with every output, Every model file recorded with its licence, source and a commercial flag, Outputs on local disk with a sidecar per image, a ceiling and a retention rule. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. - Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together.
# Delivery milestones · Flair AI Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A cutout that keeps the actual product The generated scene changes everything except the item being sold. ### Steps 1. Store the mask as an asset, not as a temporary file Every rerun months later needs exactly this mask. Files: `src/masking.py` 2. Composite the original pixels back at full resolution The generated frame is a background, not a photograph of your goods. Files: `src/composite.py` 3. Measure the difference inside the mask and flag it This is the automated version of a second pair of eyes. Files: `src/composite.py`, `tests/test_mask_diff.py` ### Done when - [ ] Product pixels are unchanged inside the mask - [ ] An over-strong generation is flagged automatically - [ ] Masks are stored and reused on rerun - [ ] Transparent source images are handled ### Watch out - Running image-to-image over the whole frame at a strength high enough to make the background interesting also redraws the label, invents a plausible logo and smooths the texture of the material, and the result is a photograph of a product that does not exist. Generate the background, composite the real pixels back, and let the mask difference measurement be the thing that catches you rather than a customer. ## M2 · Jobs you can actually run again An output records everything needed to reproduce it, and says so when it cannot. ### Steps 1. Store the whole graph, not a summary of it The graph is the recipe and the seed is one ingredient. Files: `src/jobs.py`, `src/backend.py` 2. Hash every model file involved Names are reused; contents are not. Files: `src/registry.py`, `tests/test_rerun.py` 3. Compare the rerun and report the difference An honest mismatch is far more useful than a silent one. Files: `src/rerun.py` ### Done when - [ ] A rerun on an unchanged backend matches - [ ] A swapped model file is detected by hash - [ ] Backend version is recorded per job - [ ] A down backend fails clearly and is resumable ### Watch out - Recording the seed and the prompt but not the workflow graph or the model file hashes gives you a rerun that looks like it should work and produces a different picture, because a node was updated, a LoRA was replaced with a same-named file, or the sampler default changed underneath you. Store the graph and the hashes, and treat a differing rerun as information rather than a failure to hide. ## M3 · The licence gate and the missing-file problem A model cannot run in a project its licence forbids, and a missing file is never a silent substitution. ### Steps 1. Make licence and source required registry fields A blank licence field is how a legal problem is stored. Files: `src/registry.py` 2. Refuse rather than warn on a licence mismatch The warning is read once and dismissed forever. Files: `src/gate.py`, `tests/test_licence_gate.py` 3. Verify referenced models by hash before submitting The backend's helpfulness is the problem here. Files: `src/jobs.py` ### Done when - [ ] A non-commercial model is refused in a commercial project - [ ] A missing model refuses the job before submission - [ ] No registered model has a blank licence - [ ] Memory estimates appear before a run ### Watch out - A workflow referencing a LoRA that is not installed often runs anyway, because the backend skips the node or falls back to the base model, so two hundred images are produced in the wrong style and nobody notices until they are compared against the first test image. Check every referenced model exists by hash before submitting, and refuse the job rather than letting the backend improvise. ## M4 · Batches that survive the hardware A run of two hundred variants can fail at sixty and continue from sixty. ### Steps 1. Write the plan before you run anything The plan is what makes resume possible at all. Files: `src/batch.py` 2. Execute serially with a timeout per job One card, one job, and a stall that ends. Files: `src/batch.py`, `tests/test_resume.py` 3. Detect the backend dying and wait for it Out of memory is a normal Tuesday, not an exception. Files: `src/backend.py` ### Done when - [ ] A killed backend resumes without duplicates - [ ] Timeouts fail one variant, not the batch - [ ] The plan exists before execution begins - [ ] Individual variants can be retried ### Watch out - Firing the whole batch at the backend at once means an out-of-memory failure halfway through takes the process with it, and since nothing recorded which variants had already been produced the only option is to run all two hundred again, several hours of a card you are paying for one way or another. Persist the plan first, execute one at a time, and make resume the default. ## M5 · The contact sheet, the sidecar and the disk (production only) Choosing an image is fast, every exported file carries how it was made, and the disk has a ceiling. ### Steps 1. Filter the contact sheet by licence status Then you can answer the listing question in one click. Files: `src/views/sheet.tsx` 2. Write a sidecar beside every export The image and its provenance travel together or not at all. Files: `src/export.py`, `tests/test_sidecar.py` 3. Cap the disk and retain favourites only A batch run is thousands of files you will never open again. Files: `src/retention.py` ### Done when - [ ] Every export has a sidecar with models and licences - [ ] The sheet filters by licence status - [ ] Retention deletes non-favourites and logs it - [ ] Deleting a project removes files and rows ### Watch out - Exporting a chosen image on its own separates it from every record of how it was made, so six months later the question of which model produced the listing photograph, and whether that model's licence allowed it, has no answer and the image cannot be regenerated at another size. Write the sidecar beside every export and keep them together.
# Operations · Flair AI ## Backup The database and the sidecars matter more than the images, because an image with its provenance can be regenerated and an image without it cannot. Back up source photographs and masks with the same care as the database; they are the only irreplaceable files here. ## Restore Restore the database, the sources and the masks, then confirm a stored job reruns to the same hash on the restored backend. A mismatch tells you the backend changed, which is worth knowing before a client asks for the same shot at another size. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Backend availability, jobs stuck in progress, batch completion against plan, outputs failing the mask difference check, and disk against the ceiling. ## Incident checklist A batch of images in the wrong style almost always means a referenced model was missing and the backend improvised, which the pre-submission hash check is there to prevent. An output that fails the mask check means the generation strength is too high for that mask. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Product pixels are confirmed unchanged inside the mask - [ ] A rerun on an unchanged backend is confirmed to match - [ ] A non-commercial model is confirmed refused in a commercial project - [ ] A killed backend mid-batch is confirmed to resume without duplicates - [ ] Every export is confirmed to carry a sidecar with models and licences ## Launch constraint Do not market omitted Flair AI capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Localhost only. There is no account system and there should not be. APP_PORT=8020 # Required. Your own generation server. The only place jobs are ever submitted. BACKEND_URL=http://127.0.0.1:8188 # Required. Projects, models, jobs, outputs and batch plans. DATABASE_PATH=./data/studio.db # Required. Images and their sidecars, kept together. OUTPUT_DIR=./data/outputs # Required. The disk ceiling. Retention runs against it rather than against hope. OUTPUT_MAX_BYTES=214748364800 # Required. Above this, the product pixels moved and the output is not shippable. MASK_DIFF_THRESHOLD_PCT=0.5 # Required. One variant fails and the batch continues. JOB_TIMEOUT_SECONDS=600 # Required. Favourites are kept. The other thousands are not. RETAIN_NON_FAVOURITE_DAYS=30
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Flair AI because the product value is the model and compute fleet, not the prompt box around it. The recurring cost buys GPU procurement, model licensing, safety filters, queueing, storage, and rapid model replacement, not just the visible interface.
xspecialized product-photo workflow, templates, model quality, and hosted rendering
xfrontier proprietary models
xhosted GPU capacity
xlicensed training data
xmoderation and fast global delivery
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
Flair AI pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| free | $0 | $0 | 1 custom model, 5 generated images and 1 video; Instant/Ad mode consumes 4x the normal image quota. |
| pro | $8 | — | 2 videos; the official rendered page did not expose the numeric image or custom-model allowance. |
| pro+ | $26 | — | Up to 8 standard or 2 fast custom models, 80 images and 3 videos; Instant/Ad mode costs 4x quota. |
| scale | $38 | — | Tier 1 includes up to 15 standard or 4 fast custom models, 150 images and 5 videos. |
| enterprise | custom | — | Custom model, API, security and volume terms; public price is not listed. |
free tier1 custom model, 5 generated images and 1 video.
billingmonthly + annual; annual USD amounts were not exposed in the public rendered page
hidden costsInstant and Ad generation modes consume 4x the normal image quota. Larger Scale allowances and API access can require higher/custom tiers.
verified 2026-08-14 · source ↗
Vibecode Flair AI
Kinda. The core of Flair AI is buildable in a weekend with the prompt on this page, but there are real gaps: specialized product-photo workflow, templates, model quality, and hosted rendering, frontier proprietary models. Read the honest list above before committing.
How much does Flair AI cost?
Flair AI costs about $8/month (Pro, checked 2026-08-14), which is $96 per year.
What do I lose by replacing Flair AI?
Honestly: specialized product-photo workflow, templates, model quality, and hosted rendering; frontier proprietary models; hosted GPU capacity; licensed training data; moderation and fast global delivery. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Flair AI?
Yes: Krita AI Diffusion (Mask the product, paint the rough set and generate the background; more manual, but every layer is yours.) NodeTool (Cut out the product, arrange props, relight and generate a scene in one local sketch-and-node workspace.) The prompt is for when you want it exactly your way.