Inspiration
Every app that references media by a key eventually hits the same problem: the asset exists, or the page breaks.
Fixing a missing asset usually means leaving the app, generating the file somewhere else, downloading it, uploading it to a storage bucket, connecting the new URL, and manually tracking where it came from. Image generators create files, storage buckets hold files, and CDNs cache files, but none of them treats a missing storage key as an instruction to create the asset.
I built Qavelys to make storage close that gap automatically, while keeping a verifiable record of every step.
What it does
Qavelys is a generative storage origin.
An application requests an asset using the exact key it expects. When the key already exists, Qavelys returns the stored Backblaze B2 object like a normal storage origin.
When the key is missing, Qavelys automatically:
- Refines the request prompt using Groq.
- Generates multiple candidate assets through Genblaze.
- Runs each candidate through a deterministic quality gate.
- Selects and promotes the winning asset.
- Stores the source candidates, rejected candidate, rejection reason, manifest, final asset, and canonical receipt in Backblaze B2.
- Returns the final asset to the requesting application.
Every later request for the same key becomes a standard B2 hit. The exact same bytes are returned with no repeated generation cost.
Each generation run also produces a hash-verified receipt. Anyone can independently recompute and verify the receipt through Qavelys’ public verification endpoint.
How I built it
Qavelys uses a FastAPI backend to manage the origin request flow, storage operations, generation providers, quality checks, receipts, and verification.
Backblaze B2, accessed through its S3-compatible API, acts as the complete system of record. Qavelys does not use an application database. Assets, manifests, failures, generation evidence, and receipts are all stored directly in B2.
The generation pipeline uses:
- Groq with
llama-3.3-70b-versatilefor prompt refinement. - Genblaze for generation orchestration.
- NVIDIA NIM with
black-forest-labs/flux.2-klein-4bfor image generation. - Provider fallback logic for handling failures and unavailable services.
Before any generated asset can be promoted, it passes through a deterministic quality gate that validates:
- The real file format.
- Image dimensions.
- File size.
- Required metadata.
- Asset integrity.
Receipts are stored as canonical JSON. Every source candidate, rejected asset, final asset, and manifest is recorded using SHA-256 hashes, making the complete run independently verifiable.
The frontend is built with React, TypeScript, Vite, Tailwind CSS, and includes:
- Origin Studio for testing generative asset requests.
- A receipt and integrity verification interface.
- An in-app documentation portal.
- Request, generation, cache-hit, and failure views.
The application is deployed as a Docker service on Render. Cloudflare Worker and Railway deployment configurations are also maintained in the repository.
The project currently includes:
- 53 backend and CLI tests.
- 26 Playwright browser tests.
- A 30-request production availability test.
- Public verification for generated receipts.
Challenges I ran into
One of the biggest challenges was provider reliability.
GMI Cloud began returning HTTP 402 responses because of insufficient credits during development, so I replaced the image generation provider with NVIDIA NIM.
My first NVIDIA model choice, flux.1-schnell, repeatedly timed out. I moved to flux.2-klein-4b, which provided a more reliable generation path.
I also ran into several Backblaze B2 configuration issues. The S3 endpoint was initially set to the wrong region, using us-west instead of us-east. This caused authentication failures that were difficult to trace. I also mixed up the B2 bucket ID and application key ID during configuration. Correcting both resolved the storage failures.
Another real production issue came from prompt length. Groq could return a refined prompt longer than NVIDIA NIM’s 800-character request limit. This caused a provider failure that Qavelys correctly recorded in a failure receipt. I fixed it by adding a deterministic prompt cap before generation.
Deployment also introduced an unexpected routing issue. Render’s Ohio edge intermittently returned 404 responses even though every request that reached the container returned HTTP 200. I redeployed the service in Virginia and confirmed stability with a 30 out of 30 successful availability sample.
Accomplishments that I’m proud of
Qavelys is live, and its proof can be checked during a real production run.
A production request successfully:
- Triggered generation from a missing key.
- Generated multiple candidates.
- Stored the winning asset.
- Stored a rejected asset with its rejection reason.
- Created a canonical receipt.
- Passed all 9 integrity checks.
- Returned a byte-identical B2 cache hit on the next request.
The provenance system is based on reproducible hash verification. Changing even one byte of a stored artifact causes verification to fail.
Generation also fails closed. New asset generation requires a valid server-side bearer code, and usage quotas apply only to cache misses. Existing stored assets continue to behave like normal B2 objects.
Qavelys does all of this without an application database. Backblaze B2 receipts are the permanent record.
What I learned
This project changed how I think about storage.
Storage can act as a runtime primitive instead of serving only as a passive place to keep files. A missing object can become a meaningful event that triggers generation, validation, storage, and provenance.
I also learned that deterministic verification is stronger than asking users to trust a dashboard. A receipt that anyone can recompute provides more useful proof than an interface simply claiming that a file is valid.
Keeping failure evidence also matters. Rejected candidates, provider failures, and error receipts make the system easier to debug and prevent the pipeline from hiding unsuccessful runs.
Provider fallback became another major lesson. Free and hosted AI services can rate-limit, time out, lose credits, or return unexpected errors during a live demo. A production generation system needs to expect those failures and preserve evidence when they happen.
What’s next for Qavelys
The next major step is a signed S3 proxy so Qavelys can work directly behind existing S3 clients, rather than requiring applications to use its HTTP origin API.
I also plan to add:
- Video asset generation through the same storage, quality gate, and receipt pipeline.
- A reviewed Backblaze B2 lifecycle policy for rejected candidates and failure artifacts.
- Shared coordination for generation rate counters.
- Distributed locking for asset keys.
- Multi-worker support for horizontal scaling.
- More generation providers and richer asset validation rules.
- Signed receipts for stronger third-party verification.
The long-term goal is to make generative storage feel like a normal storage primitive: request a key, receive the asset, and verify exactly how it was created.
Log in or sign up for Devpost to join the conversation.