An image is not a sentence.
A caption flattens a scene into a string. A scene graph keeps the structure: who is where, holding what, doing what to whom, in what kind of place. VSON is one notation for that graph, designed so any vision-capable model can produce it and any database can read it.
What you just saw
You uploaded an image. Your browser sent it straight to a vision-language model through
OpenRouter, on your own key — the request never touches the studio host — with a 5 KB system prompt that lists the closed vocabulary, the
five hard rules, and a worked example. The model emitted a Penman tree — text that looks
like nested S-expressions. A Pyodide worker, also in your browser, rewrote that tree into
Turtle 1.2 with the reference emitter CI byte-compares against the vson CLI, then
checked it with the same two gates the CLI runs. If it conformed, you saw the graph. If it didn't,
the studio fed the violations back to the model and asked for a fix, up to twice.
The image goes from your browser to OpenRouter and nowhere else. There is no studio backend to store, log, or cache it. Only the resulting envelope (≤8 KB of text) is small enough to keep around.
Why a graph?
A caption is fine for a search index. It is wrong for everything else. "A queen with a crown stands behind a knight" hides the spatial relation, fuses two entities into one phrase, and offers no way to say what the knight is holding. Try to query it. Try to merge it with another caption of the same scene. It melts.
A graph is the format the rest of the stack already wants. Knowledge graphs, retrieval, 3D reconstruction, scene-aware tools, simulators, evaluation harnesses — they all consume edges, not strings. VSON is what the model emits when you ask for the same data the downstream code is going to need anyway.
Why these primitives?
- Frame
- The world from a viewer's chair:
SceneContext(where),VisualStyle(how rendered),CameraView(from where). Every directional spatial fact must name a viewer; perspective is not optional. - Entity
- What the scene contains:
PhysicalObject,Aggregate,Substance. Carries traits — animacy, countability, individuation — derived from the class, not re-inferred per call. - Quality
- Properties that vary independently of identity: color, material, affect, action-state. Reified as their own nodes so they can be annotated, cited, or revised without rewriting the entity.
- Event
- A predication with thematic roles:
:agent,:patient,:instrument. Actions are nodes, not edges, so a third party can attach confidence, time, or counter-claims to them. - SpatialFact
- RCC-8 topology + Talmy-style direction + proximity, all reified as a single node so a viewer can be attached. "left of" is meaningless without saying left of whom.
The full vocabulary lives in docs/vson.md. It is closed: out-of-vocabulary tokens fail SHACL by design.
How it stays correct
Every document is checked against a published SHACL shape graph before it is returned. The shapes encode the constraints that matter most:
- Every directional
SpatialFactmust carry a viewer. - Every
Eventmust carry a lemma. - Every
Qualitymust carry both a dimension and a value. - Bounding boxes are normalized to
[0,1]. - Trait values come from a closed enumeration.
All of it runs in your browser: a Pyodide worker executes pyshacl over the shapes (Gate 1),
then an owlrl OWL 2 RL consistency check (Gate 2) — the same two gates, in the same order,
as vson validate. The first live validation downloads ~16 MB of runtime (less
over the wire with compression); after that it is cached.
If the model emits a non-conformant document, the studio feeds the SHACL report back to the model and asks it to repair. Up to two repair rounds, then it ships whatever it has with the violations attached so you can see what failed. No silent corrections.
Why it's free
The five demo images were extracted once, for real, and their envelopes are committed to the repo with the model's genuine provenance. Clicking a thumbnail fetches the baked envelope — no model call, no API key, no spend. The sixteen gallery scenes are hand-authored conformant fixtures served the same way. That is the keyless $0 path. A new upload is different: it goes from your browser straight to OpenRouter on your own key — your spend — and never routes through the studio host. Image bytes are never persisted, in either path.
The studio is stateless. Refresh the page and the scene is gone. There is no account, no database, no session. The only durable artifact of a run is the envelope you choose to download.
Why it's open
The ontology, the shapes, the CLI, and the studio are all Apache-2.0. The vocabulary maps directly to AMR (for actions), Cypher (for property graphs), and Visual Genome (for image-graph corpora). Anyone can extend the vocabulary, ship a model that emits VSON, or build a consumer that reads it. The studio is one such consumer; the vson-extractor skill is one such producer.
What's next
An IndexedDB envelope cache so re-uploading the same image is free for you too, and
streaming responses. The ontology itself is published at vson.pages.dev under the w3id.org/vson names. Status, in honest terms, is on GitHub.