We will be undergoing planned maintenance on Oct 7th 6:00AM UTC / Oct 7th 2:00AM ET

Inspiration

Creative and marketing teams are already shipping AI-generated media in client work, but almost none of them can answer the follow-up questions: which model made this, from what prompt, and is this file the exact one that was approved? Client disclosure requests are becoming normal, transparency obligations for AI-generated content are emerging in frameworks like the EU AI Act, and the industry is converging on provenance standards like C2PA. We wanted the workflow tool that makes provenance the default output of generation, not paperwork bolted on afterwards.

The insight that shaped the build: the people who most need that proof are the ones outside the tool. The client, the reviewer, the regulator. A provenance record that only opens inside our own dashboard would answer the question for the one person who never had to ask it. So the proof had to be signed, embedded in the media, and checkable by a stranger holding nothing but the file. Genblaze's manifest and agent primitives plus B2's durable object storage made that a weekend-buildable idea.

What it does

You write a creative brief: title, description, target audience, modalities (image / video / audio), style, number of variants, and a quality threshold. Provenant then:

  1. Runs an agentic pipeline (Genblaze AgentLoop) that generates candidates across providers, scores each one with an evaluator, and retries sub-threshold outputs with the evaluator's feedback up to 3 iterations per candidate.
  2. Stores winning assets on Backblaze B2 under content-addressed keys (provenant/assets/<aa>/<bb>/<sha256>.<ext>), each with a provenance manifest recording provider, model, prompt, seed, parameters, scores, retries, costs, and a canonical hash.
  3. Signs each asset with an Ed25519 credential binding its content hash to its manifest hash, and registers it in a hash-keyed ledger on B2.
  4. Serves a searchable library with thumbnails, full lineage per asset, and one-click verification of both integrity (hashes) and authenticity (signature + issuer).
  5. Delivers proof with the file. Downloads carry the credential embedded in the media's own container (PNG iTXt, JPEG APP11, GIF comment, WAV chunk, MP4 uuid box, MP3 ID3), following C2PA's hard-binding model: the credential commits to the hash of the file before embedding, and verification strips it back out before hashing.
  6. Lets anyone verify. A public /verify page takes an uploaded file no account, no asset id and returns the whole chain: credential found, signature valid, content binding intact, issuer trusted, registered in the B2 ledger. One flipped byte reads Tampered.

It runs in two modes: demo (Genblaze mock providers synthesize real placeholder media locally; zero API keys; the full pipeline path is real) and live (Pollinations / Hugging Face / ElevenLabs plus a real B2 bucket). A toggle in the UI flips a running server between them without a restart.

How we built it

  • Backend: Python + FastAPI, exposing a small REST contract (/api) with OpenAPI docs. One job per brief, fanning out into steps per modality and variant.
  • Generation: Genblaze AgentLoop with a pipeline_factory that rebuilds a refined Pipeline from evaluator feedback each iteration; threshold evaluators gate on the brief's quality bar; steps declare fallback_models for model failover.
  • Quality gate: a deterministic byte-level heuristic by default (image entropy, colour diversity, contrast, detail; audio RMS, duration, spectral tone count via a windowed Goertzel scan), upgrading in live mode to a vision-LLM judge that scores the image against the actual brief.
  • Storage: Genblaze ObjectStorageSink with content-addressable key strategy under a provenant/ prefix. Live mode uses S3StorageBackend.for_backblaze; demo mode uses a local subclass of the SDK's abstract StorageBackend, so both modes share one code path and key scheme.
  • Provenance: Genblaze Manifest objects stored as JSON on B2 next to the assets, countersigned into the SDK's reserved signature field; the library index is a rebuildable cache derived from them.
  • Signing and embedding: Ed25519 via cryptography; hand-written, byte-reversible container writers for PNG, JPEG, GIF, WAV, MP4, and MP3.
  • Frontend: Vite + React 18 + TypeScript + Tailwind: brief form, live job progress (per-step status, attempt counter, score meter, evaluator feedback), searchable library, asset detail with manifest and lineage, verify and deliver panels, and a public drag-and-drop verification page.

Challenges we ran into

  • Embedding proof without invalidating it. Writing a credential into a file changes the file, so the credential cannot assert its own container's hash. We adopted C2PA's hard-binding model: commit to the hash before embedding, and strip the credential back out before re-hashing. That meant hand-writing byte-exact container surgery for six formats PNG chunks, JPEG segments, GIF extensions, RIFF chunks, MP4 boxes, ID3 tags because re-encoding through an imaging library would have changed every other byte too. strip(embed(x)) == x is the invariant, and every format has a test asserting it, including an MP3 that already carried its own ID3 tag.
  • Separating integrity from authenticity. Our first verification was a hash check, and hash checks are circular: anyone who can rewrite the bucket can rewrite the asset, the manifest, and the canonical hash together and pass. Signing fixed it, but only once we stopped collapsing signature_valid and issuer_trusted into one boolean a validly signed credential from an unknown key is exactly what a forger produces.
  • Designing demo mode so it is honest: mock providers had to synthesize real files so hashing, manifests, evaluation, sinks, and now signing exercise the same path as live mode, rather than a faked UI.
  • Making the feedback loop genuinely agentic: threading evaluator feedback through AgentContext into the next pipeline build so retries actually change the prompt, not just re-roll it.
  • Keeping the index disposable: resisting the urge to make the app database authoritative, and instead deriving everything from B2 so the library is rebuildable from the bucket alone.
  • Reaching a free tier at all: an NVIDIA developer key reaches the chat models but 404s on media generation, so we wrote our own keyless Genblaze provider rather than gate the whole product behind credentials.

Accomplishments that we're proud of

  • Provenance that outlives the app. A downloaded file carries its own signed credential, and scripts/verify_offline.py checks it with no server, no network, and no shared verification code with the backend deliberately, because sharing code would weaken the claim it exists to support.
  • Verification is real, not decorative: stored bytes re-fetched, SHA-256 recomputed, canonical hash re-checked, signature verified, issuer compared against the published key. Flip one byte anywhere and it fails.
  • Third parties are first-class users. The person who most needs provenance has no account, and now doesn't need one.
  • The whole product runs with zero keys in demo mode all three modalities, sealing and verification included and flips to real providers and real B2 from the UI without a restart.
  • Failure is a modeled state: model failover, bounded retries with feedback, partial job semantics, and per-step errors, all visible in the UI.
  • 75 tests, fully offline, covering the forgery cases as well as the happy path.

What we learned

  • Genblaze's primitives map remarkably directly onto a provenance product: AgentLoop is the retry loop, Manifest is the audit record, ObjectStorageSink is the storage policy. Most of our backend is composition, not invention and where it wasn't, the SDK had already left the seam open (Manifest.signature, custom SyncProviders, StorageBackend subclasses).
  • Content-addressed keys simplify more than storage: dedup, integrity checking, cache-busting, and the public hash-ledger lookup all fall out of one decision.
  • "Index is a cache, storage is truth" is a forcing function for good architecture; it kept state where it belongs, and it is why public verification could be built without touching the database.
  • A provenance system that only works inside its own UI has not really solved the problem it names.

What's next (Roadmap)

  • Broker-backed job queue and worker pool for restart-safe, horizontally scalable generation.
  • B2 Object Lock (ObjectLockConfig) for WORM manifests and credentials tamper-evidence against the operator, not just the delivery chain.
  • Signing key in a KMS, with rotation and a published key history so credentials under retired keys stay verifiable.
  • Full C2PA interoperability: emit a standard manifest store alongside our compact credential, so assets verify in tools that already speak it.
  • AuthN/AuthZ and multi-tenancy (Genblaze pipelines already carry tenant_id).
  • Lifecycle rules to tier or expire rejected candidates while retaining assets, manifests, and credentials.

Built with

  • Python, FastAPI, Uvicorn
  • Genblaze SDK (genblaze, genblaze-core, genblaze-s3; provider plugins: genblaze-openai, genblaze-elevenlabs, genblaze-nvidia, genblaze-google)
  • Backblaze B2 Cloud Storage (S3-compatible API, presigned URLs)
  • Pollinations.AI, Hugging Face Inference Providers, ElevenLabs, NVIDIA NIM, Google Gemini, OpenAI
  • Ed25519 signing via cryptography
  • Vite, React 18, TypeScript, Tailwind CSS
  • Pillow (thumbnails / demo media synthesis)

Built With

  • css
  • elevenlabs
  • fastapi
  • google-gemini
  • hugging-face-inference-providers
  • nvidia-nim
  • openai-ed25519-signing-via-`cryptography`-vite
  • pillow
  • python
  • react-18
  • tailwind
  • typescript
Share this project:

Updates

Submission history