Penman. Nested S-expressions, AMR-style. Verbose but easy to read; paren count diagnoses most failures. The studio default.
Vision-LLMs invent scenes. SHACL doesn't lie.
The trick is sticking a closed vocabulary between them — small enough to fit in a system
prompt, expressive enough to encode a picture, strict enough that violations point
somewhere. One RDF graph underneath, three surface notations on top. Same predicate logic
either way: contains(scene, p) ∧ class(p, Person) doesn't change because you wrote it with sigils
instead of parens.
Eight prefix sigils, line-significant, no brackets. Cheaper in tokens. Graph-equivalent to Penman across the gallery; one documented collapse on symmetric spatial facts.
The eighteen-kilobyte original. Closed vocabulary inline, hard rules, a worked example per primitive kind. Heaviest, and the size earns its keep when conformance matters more than tokens.
One graph, three spellings
Same scene — a red apple, eye-level close-up — written three ways. The notations are the surface; the graph is the commitment.
(scene / Composition
:framedBy (cam / CameraView :angle eye_level :framing close_up)
:depicts (apple / PhysicalObject :class Apple
:hasQuality (q / Quality :dimension Color :value red)))~scene
/CameraView @cam *angle eye_level *framing close_up
^cam
apple /PhysicalObject *class Apple *color redRender the RDF as English:
"Eye level close up, 50mm lens. A red apple."
Or as predicate logic:
Composition(scene). PhysicalObject(apple). Quality(q).
class(apple, Apple). hasQuality(apple, q).
dimension(q, Color). value(q, red). Two surface notations, one English caption, one logical form — all from one graph. The
studio's caption and fol exporters are deterministic functions of the
RDF, not separate prompts.
Wire it in
Paste the prompt body as the system message. Cache it on the system block so follow-up
calls in the same conversation pay around 10% of the input-token cost on the cached prefix.
client.messages.create(
model="claude-sonnet-4-6",
system=[{"type": "text", "text": SKILL,
"cache_control": {"type": "ephemeral"}}],
messages=[{"role": "user", "content": [
{"type": "image", "source": {...}},
{"type": "text", "text": "Emit the document."},
]}],
) Validate locally:
vson convert p2t scene.vson | vson validate /skills/<name>/ on GitHub ships each prompt with a conformance.json acceptance fixture and per-platform
code snippets.