// build-strategy / free-tier

Real quantum, 5 credits, one build message.

Every mega-prompt in this repo uses the same pattern, because it's the only pattern that lets a free-tier Lovable account ship a real Quantinuum demo in one shot.

Heads up: Guppy v1 is a breaking release

Every snippet and mega-prompt here targets Guppy v1 (Python 3.12+). If you copied anything before, three things changed: result() is now output(); measure(q) returns a Measurement, so call .read() before using it; and runs go through the emulator builder, program.emulator(...).with_shots(n).run(), instead of the old build/run-shots helper. Install with pip install "guppylang>=1.0" — Selene now ships inside it.

Why not run Guppy at runtime?

Lovable apps deploy to Cloudflare Workers (edge JavaScript). Workers cannot run Python — so calling Selene from a server function at runtime will fail. Don't burn credits trying. Instead, run the quantum circuit at build time in the Lovable Linux sandbox, commit the real output as JSON, and let the frontend read it.

The 5-step pattern

recipe
# 1. In the Lovable Linux sandbox during the build:
pip install "guppylang>=1.0"      # Guppy v1 — Selene ships inside it

# 2. Author the kernel as a REAL .py file:
#    quantum/kernel.py  (see snippet)

# 3. Author a driver that runs the kernel over a small grid and writes JSON:
#    quantum/run.py  (see snippet)

# 4. Execute the driver ONCE during the build:
python quantum/run.py

# 5. The React app statically imports src/data/quantum-results.json.
#    No Python runs at runtime. No backend. No Cloud. No auth.

1. The kernel — a real .py file

Guppy reads source via inspect.getsource, so it must be on disk. REPL strings, exec(), and Jupyter cells all fail.

quantum/kernel.py
# quantum/kernel.py — a real .py file on disk (Guppy reads source via inspect)
from guppylang import guppy
from guppylang.std.builtins import output
from guppylang.std.quantum import qubit, h, cx, measure, discard

@guppy
def swap_test() -> None:
    a = qubit()
    b = qubit()
    anc = qubit()
    h(anc)
    # ... prepare |a>, |b> however your problem encodes them ...
    cx(anc, a)
    cx(anc, b)
    h(anc)
    output("anc", measure(anc).read())     # Guppy v1: .read() is required
    discard(a); discard(b)

2. The driver — runs once at build time

Keep the grid small (5–20 inputs, ≤8 qubits, ~256 shots). The driver writes one JSON file the frontend reads.

quantum/run.py
# quantum/run.py — runs once at build time, writes real Selene output as JSON
import json, pathlib
from selene_sim import Quest                 # the emulator ships inside guppylang v1
from kernel import swap_test

OUT = pathlib.Path("src/data/quantum-results.json")
OUT.parent.mkdir(parents=True, exist_ok=True)

records = []
for i in range(10):           # candidate axis
    for j in range(10):       # reference axis
        # bind your problem inputs here (state prep, parameters, etc.)
        res = (
            swap_test
              .emulator(n_qubits=5)
              .with_shots(256)
              .with_seed(7)
              .with_simulator(Quest())
              .run()
        )
        ones = sum(int(v) for shot in res for _, v in shot.entries)
        fidelity = max(0.0, 1.0 - 2.0 * ones / 256)
        records.append({"input": {"i": i, "j": j}, "output": {"fidelity": fidelity}})

OUT.write_text(json.dumps(records, indent=2))
print(f"wrote {len(records)} records to {OUT}")

3. The frontend — pure static read

src/routes/index.tsx
// src/routes/index.tsx — frontend just reads the JSON, no runtime Python
import results from "@/data/quantum-results.json";

// every number on screen traces back to a real Selene shot
const top = [...results].sort((a, b) => b.output.fidelity - a.output.fidelity)[0];

4. Prove it — the part judges remember

Every mega-prompt now ships a verification bar. Six small steps turn a pretty chart into evidence:

  • · Baseline first. Compute the honest classical answer (cosine similarity, brute-force search) before any quantum code, and show it next to the quantum number. A degenerate encoding that scores a perfect 1.000 is caught here, never by the circuit.
  • · Pass bar. Compare shots against the NumPy reference with tol = 4·√(0.5/shots) — about 0.088 at 256 shots. The textbook 3σ form collapses near p=0 or p=1 and reports false failures.
  • · Angle sanity cell. One 256-shot single-qubit check before the sweep. angle() is in halfturns; if P(1) lands at 0 or 1 you passed radians.
  • · Honest labelling. Never show a number without its engine and shot count in the same view, and never call an emulator run "hardware" or "the QPU". Prompts now emit a small receipt block — claims, engine, backend qualifier, shots, seed, stored envelope, verdict — so a judge can re-derive the verdict.
  • · Dequantization gate. Run the classical surrogate and a 128 / 512 / 2048 shot ladder. If the classical method lands inside the same envelope, say "classically equivalent at this size" out loud. Naming your own limit wins the room.
  • · Bell control. One |Φ+⟩ pair in the same batch, near-free. Anti-correlated fraction above the envelope means the whole batch is bad — re-run it, don't report one bad row.
  • · Review the observable before you run it. Every check above tests consistency, not correctness — a wrong observable measured perfectly passes all of them. Prompts now write an experiment-spec.json first: observable, baseline, and the falsification criterion that would prove the hook wrong.
  • · Circuit audit. Gate count, 2-qubit fraction, depth and per-qubit idle windows computed from the compiled program and rendered from that file — never a number typed into page copy, which is the easiest thing for a judge to falsify.
  • · Bring real data. One public corpus beats your own generator: a synthetic run proves the code works, someone else's data proves the method transfers. Prompts now ask for a dataset-triage.json naming the source, the verdict, the parse rule and the hash of the file you actually read.
  • · "It worked" is not "it won". The shot-noise bar grades the mechanism only. Whether the quantum method beat the classical baseline is a separate verdict, reported separately — including when it lost.

If your build disappears

You may see everything complete, then an internal error, then all of it gone. The build didn't fail — the save did. Installing Python packages into .pydeps/ writes tens of thousands of files, and if that folder isn't ignored the save runs out of time and the finished work is rolled back.

  1. Add .pydeps/ to .gitignore before installing anything.
  2. Install the Python packages into .pydeps/ again.
  3. Re-run the small smoke test.
  4. Check that both the code and the smoke-test result are still there next turn, before scaling up to the full sweep.

Ignored packages stay local to the sandbox, so they may need reinstalling if the environment is recreated. That's expected and costs nothing.

Credit budget rules

  • · One mega-prompt = one build message. No iterative refinement loop.
  • · Hard scope cap: 1 page (the workspace) + an "About the quantum" strip.
  • · No accounts. No Lovable Cloud. No database. No auth.
  • · Add a "Quantum trace" disclosure that prints kernel.py inline so judges see it's real.
  • · Keep ~1 credit in reserve for a single fix-it pass after the first build.