Prodigy web GUI and analysis API¶
prodigy web starts a local web server and opens the GUI in the browser. You write a pGCL
program, choose how each while loop is analysed, and get the output distribution and the
answers to the queries — each with what is guaranteed about it.
uv sync
uv run prodigy web # http://localhost:8080
uv run prodigy --engine ginac web # GiNaC as default engine (usually much faster)
uv run prodigy web --port 9000 --time-limit 300 --no-browser
The GUI needs no Node.js or internet connection; the editor (CodeMirror 6) and KaTeX are
vendored under prodigy/web/static/vendor/.
Prodigy is tested with Python 3.11 and 3.14.
What the GUI shows¶
Program (left): editor with pGCL highlighting, syntax errors (and type-checker warnings) as you type. Above each loop a pill shows its strategy and, after a run, what it certified. After each
?Pr/?Exquery the value appears inline.Setup (right): initial distribution, one strategy per loop with its parameters, analysis options (engine, conditioning, simplification, number of coefficients, time limit, EVT synthesis strategy, solver, recording of intermediate distributions).
Result (right): a summary of the guarantee, the queries with sound intervals, the output distribution (closed form, mass, expectations, marginal bar charts and tables), the certificate of every loop, console output, and the full result as JSON.
Examples: the programs in
pgfexamples/, with a suggested setup derived from their first line (e.g.check_equalityexamples come with their invariant).New, Open…, Save (toolbar): in Chrome and Edge, Open and Save work directly on
.pgclfiles on disk, and the file stays linked across reloads; Save As… (⇧⌘S) picks another file. Safari and Firefox cannot write to a folder: Open reads the chosen file, Save downloads the program. The program is also kept in the browser, so a reload never loses it; replacing a program with unsaved changes (New, Open…, an example) asks first.
Keyboard: ⌘/Ctrl + Return analyses, ⌘/Ctrl + . (or Esc) stops, ⌘/Ctrl + O opens, ⌘/Ctrl + S saves. Every analysis runs in its own process with a time limit and can be stopped at any time.
The GUI follows the system appearance (light or dark, macOS: System Settings → Appearance); following Apple’s guidelines it has no switch of its own. Firefox can override the appearance for websites (Settings → General → Website appearance).
Guarantees¶
Every loop strategy reports what is known about its result, and the session combines this for the whole program:
Kind |
Meaning |
Produced by |
|---|---|---|
exact |
equal to the semantics |
loop-free code; unrolling/iteration that reaches a fixed point or leaves no mass in the loop; an EVT fixed point with non-negative coefficients and finite mass |
lower |
coefficient-wise below the semantics; at most the residual mass r is unaccounted for |
unroll N, iterate until mass captured, EVT iteration |
upper |
coefficient-wise above the semantics (Park’s lemma) |
invariant check (exact if the loop terminates almost surely), EVT invariants with non-negative coefficients |
unknown |
no guaranteed direction |
mixing lower and upper approximations, unchecked side conditions |
For a lower bound, every probability query becomes an interval μ_N(φ) ≤ μ(φ) ≤ μ_N(φ) + r without any termination assumption: the unaccounted mass may terminate with any value, violate an observation, or never terminate. When conditioning on observations, the residual is rescaled together with the distribution (division by 1 − known violation mass), and the interval stays sound. Expectations of monotone, non-negative expressions get the lower bound only.
EVT invariants (fixed points I = G + ⟦body⟧(⟨I⟩_guard)) are only upper bounds in general:
an invariant with infinite mass can be a spurious fixed point. The GUI checks
non-negativity with the configured positivity heuristic and decides finiteness of the mass
for rational closed forms (Pringsheim: no pole of I(t,…,t) in (0, 1]); only then is the
result reported as exact. Example: for while (x > 0) { {x := x-1} [1/3] {x := x+1} }
from x = 1, invariant synthesis finds a fixed point with infinite mass and Prodigy’s value
Pr[x = 0] = 1 is reported as an upper bound — the true value is 1/2. Unrolling 25
iterations gives Pr[x = 0] ∈ [0.4956, 1].
Two strategies give lower bounds without a termination assumption (package
prodigy.analysis.lowerbounds, see the README):
EVT sub-invariant checks a generating function ν with finite mass and ν ≤ Φ(ν); then [¬guard]·ν lies below the output (lower induction), and at most |Φ(ν)| − |ν| mass is missing. If that is 0, the result is exact.
EVT sandwich synthesises a finite-mass sub-invariant and a super-invariant (Park) by linear programming and checks both exactly. The ?Pr and ?Ex queries directly after the loop get both sides from the two certificates — expectations included, which otherwise only have a lower bound. For example, the random walk with a step counter in
pgfexamples/lower_bounds/sandwich/rw_counter_23.pgclgives E[c] ∈ [2.99948, 3.00815]. Queries after other instructions fall back to the residual mass. Needs a rational input distribution and noobserve.
Candidates from a language model¶
The strategy EVT invariant from a language model (ai_invariant) asks a language model for an
EVT invariant or a finite-mass EVT sub-invariant (parameter Kind of candidate). Prodigy checks
every candidate exactly, like one typed in by hand, so the guarantee never depends on the model.
A rejected candidate goes back to the model with the reason (e.g. Φ(I) − I), up to Attempts
times. The certificate names the model and the accepted candidate; with that candidate, the check
can be repeated without the model (strategies evt_invariant, evt_subinvariant).
The API key is read from the environment of prodigy and never reaches the browser:
export ANTHROPIC_API_KEY="sk-ant-..." # Claude API
export PRODIGY_AI_MODEL=claude-opus-5-5 # optional, default claude-sonnet-5-5
uv run prodigy --engine ginac web
# or an OpenAI-compatible API, e.g. a local model served by Ollama:
export PRODIGY_AI_BASE_URL=http://localhost:11434/v1 PRODIGY_AI_MODEL=<model>
ANTHROPIC_WORKSPACE_ID is needed for a Claude API key that is not scoped to a workspace,
OPENAI_API_KEY for OpenAI’s API; PRODIGY_AI_PROVIDER (anthropic or openai) chooses if
both are configured. Answers are untrusted input: a candidate may only contain numbers, the
program’s variables and parameters, + - * / ^, parentheses, sqrt and exp before any
computer algebra system sees it.
Non-interactive analysis API¶
The while handler no longer asks questions itself. An AnalysisSession
(prodigy/analysis/session.py) carries one decision per loop; loops are identified by
their index in textual order (prodigy outline FILE lists them).
from prodigy.pgcl import parse_pgcl
from prodigy.analysis.session import AnalysisSession
session = AnalysisSession(parse_pgcl(source), source=source,
decisions={"0": {"strategy": "unroll", "params": {"iterations": 50}}},
capture_output=True)
result = session.run().to_json() # schema "prodigy/analysis-result@1"
From the command line:
prodigy analyze prog.pgcl --loop 0=unroll:iterations=50 --json
prodigy analyze prog.pgcl --loop '*=evt_synthesis' # same strategy for all loops
prodigy analyze prog.pgcl --decisions decisions.json --output result.json
prodigy loop_strategies # strategies and parameters
prodigy main prog.pgcl --loop 0=unroll:iterations=10 # interactive, with presets
prodigy main without --loop behaves exactly as before (same menu, same prompts).
Adding a loop strategy¶
from prodigy.analysis.loop_strategies import Bound, LoopOutcome, LoopStrategy, ParamSpec, register_loop_strategy
def run(task): # task.instruction, task.distribution, task.param("n"), task.progress(i, n), ...
...
return LoopOutcome(distribution, error_prob, kind=Bound.LOWER, residual=r,
certificate={"type": "my_rule", ...}, messages=["..."])
register_loop_strategy(LoopStrategy(
name="my_rule", title="My rule", menu_text="My rule (CLI menu text)", soundness=Bound.LOWER,
description="One or two sentences for the GUI.", run=run,
params=(ParamSpec("n", "int", "Iterations", prompt="Iterations: ", default=10, minimum=0),)))
The strategy then appears in the CLI menu (after the six historic entries), in
prodigy analyze, and in the GUI’s strategy menu with a form for its parameters.
HTTP API (used by the GUI)¶
Endpoint |
|
|---|---|
|
version, engines, loop strategies with parameter specs, limits |
|
diagnostics |
|
starts an analysis ( |
|
state ( |
|
cancels the analysis |
|
example library and suggested setup |
Security and hosting¶
The server binds to localhost. API requests must be JSON and come from the page itself (Host and Origin are checked), which keeps other web sites from driving a local server.
Prodigy parses distributions and invariants with SymPy, whose parser evaluates Python code.
Do not expose the server to untrusted users without a sandbox (container without
network and file system access, resource limits). For a hosted teaching instance:
--host 0.0.0.0 --allowed-host prodigy.example.org --max-jobs 4 --time-limit 60 --memory-limit 2048 behind a reverse proxy, inside such a container.
Design notes¶
The interface follows Apple’s Human Interface Guidelines as far as they apply to a web app (foundations: accessibility, colour, typography, layout, writing).
Colours are tokens by role (
prodigy/web/static/app/app.css), with light, dark and increased-contrast variants and no app-specific appearance switch. Text contrast on the window and content surfaces: text 16.8/15.5:1 (light) and 15.6/12.8:1 (dark), secondary text 5.1/4.7:1 and 6.6/5.4:1; status colours 4.95–6.8:1 (light) and 5.0–8.9:1 (dark).One colour, one meaning: green = exact, purple = bound, orange = no guarantee, red = error, blue = interaction. Status is never conveyed by colour alone (glyph ✓ ≥ ≤ ! and a word).
The signature element is the sandwich: what is certain is drawn solid, what is only bounded is hatched — in the interval bar of each query and as the “?” bar of the residual mass in the marginal charts.
System font for interface text (13 px body, macOS scale), monospace for code and exact values, KaTeX for closed forms. Controls are at least 28 px high.
Reuse in VS Code¶
prodigy/web/static/app/components/ (result view, charts, formatting) depends only on the
result JSON, the CSS variables of result-view.css and optionally KaTeX, so a VS Code
webview can import it unchanged and map the variables to --vscode-* theme colours.
Updating the vendored libraries¶
See prodigy/web/frontend/README.md (npm install && npm run build).