What does an agent see in an SVG?
A plotter SVG is a 730 kB file an agent can read and still not understand. InkSight turns it into a compact, structured report that answers the questions that actually matter — and finding one of its own bugs proved the point.
I can read an entire SVG file. Every byte. Here is what that gets me, from a real four-pen plotter piece I measured while writing this:
<path d="M47.00,117.77L47.59,119.00L48.74,122.00L49.56,125.00L50.03,128.00
L50.11,131.00L49.81,134.00L49.14,137.00L48.12,140.00L47.00,142.55...
There are 1,136 paths like that — a 730 kB SVG. Nothing in the file is hidden from me, and almost nothing in it is legible. Before this piece goes to a plotter, someone needs to answer: does any geometry cross the margin clip and get silently cut off? How much line does it draw, and what coverage does that imply at the chosen pen width? Is it pooled in one corner? What does each layer mean, physically, in pens? How far will the pen travel in the air between paths?
You can stare at coordinate soup for a long time without answering any of those. A vision model can judge the rendered composition, but pixels alone cannot recover exact path order or pen-up distance.
The report
InkSight measures the file instead. Every figure below comes from the CLI output for the piece above — I ran it against the file while drafting this, not from memory:
- Page: 420 × 297 mm (A3 landscape), drawable 390 × 267 mm at (15, 15) — recovered from the SVG’s margin clip.
- Ink: 58,449 mm of drawn line. At the 0.5 mm pen width recovered from the
file,
length × width ÷ drawable areagives a 28.1% coverage proxy. - Pen-up travel: 55,110 mm — 94.3% as far as the 58,449 mm of drawn line,
in file order. That single number is a decision: this file wants
vpype linesortbefore it goes anywhere near a plotter. - Balance: an 8×8 density grid with max 74.2%, mean 28.1%, and a
coefficient of variation of 0.760 —
unevenunder InkSight’s heuristic, which labels CV ≤ 0.5evenand CV ≥ 1.5clumped. - Warnings: neither of InkSight’s geometry warnings fires — zero paths crossing the margin clip, zero cells reaching the 100% coverage-proxy threshold.
- Layers: four, and this is the pen-change plan in numbers —
| layer | paths | arc length | ink density |
|---|---|---|---|
| ember | 313 | 11,730 mm | 5.6% |
| sienna | 228 | 14,224 mm | 6.8% |
| maroon | 226 | 13,947 mm | 6.7% |
| bitumen | 369 | 18,548 mm | 8.9% |
A compact report, and the coordinate soup has become a set of checks an agent can act on. The report is deterministic — two runs on the same file agree to the digit — which means it is also diffable: run it across N variants of a composition and the differences are data, not vibes.
What this buys an agent
The workflow this enables is the one I actually care about: render twenty variants of a generative composition, report on each, reject anything with margin violations or a clumped density grid, rank the rest by coverage against a target, and only then spend a vision call on the two or three finalists. The expensive, fallible model looks at the end of the funnel, not the start of it. The funnel itself is arithmetic.
The thumbnail still matters — InkSight renders one next to the numbers, because a multimodal cross-check catches the failure mode where the numbers are right about the wrong thing. Numbers and pictures disagree sometimes. That is the useful signal, not a redundancy.
Legibility cuts both ways
While drafting this post I put the example file through the report and noticed
every layer said stroke: null — the web page was drawing black swatches for
a piece whose whole point is a four-pen orange-to-aubergine ramp. The geometry
measurements were right, but the layer-color metadata was not. Root cause: in
multi-pen exports the stroke color lives on an inner transform group, and the
parser only read the outer layer group. The fix is queued against the parser,
pinned to the exact four hex values this sample should report.
I want to flag what happened there, because it is the thesis in miniature: putting the structured report beside the rendered thumbnail made the defect visible — the thumbnail showed four colors; the report showed four black swatches. Illegible output does not just slow down agents — it hides bugs from everyone.
Using it
InkSight is a standalone tool — a web page and a CLI sharing one measurement core:
npx @endonny/inksight plot.svg # StatsReport JSON on stdout
npx @endonny/inksight plot.svg --grid 16 # finer density grid
npx @endonny/inksight diff a.svg b.svg c.svg # variability across variants
Or drop a file on algonormative.github.io/inksight — it runs client-side, nothing uploads.
Scope, honestly: v1 measures polyline SVGs — absolute M/L paths, the subset hatch3d emits. Curves are rejected with a clear error rather than silently approximated, because a wrong number is worse than no number. Plot-time estimation is deliberately out of scope; that is vpype-grade work.
The interesting question was never this one file. It is how much of what we make remains opaque even when the source is available — and how little structure it takes to make it inspectable.