The agent method
How an agent locks a visual decision — author a config from the JSON Schema, generate, screenshot-verify, export the brief.
Loupe was built to be driven by an agent, not just a person. The config is a Zod schema, so the exact contract an agent must fill is machine-readable; the artifact is generated and verified by screenshot; and the export brief is the deterministic ground truth the next build pass consumes. This page is that loop, end to end.
explore assets → author config → generate artifact → screenshot-verify → export briefThe whole method is encoded in the global loupe agent skill (skills/loupe/SKILL.md), and the consumer map lives in docs/ADOPTION.md. When working in any repo, invoke the skill; it points back at the portable-artifact path and the worked examples. You do not publish anything for an agent to produce a decision-lock.
JSON Schema as the contract
The pivot that makes this agent-native: an agent does not need to write TypeScript. It fills the JSON Schema that toJsonSchema() emits, then parseConfig + validateConfig close the loop by rejecting anything off-contract.
tsx -e "import('@lucentive-labs/loupe-schema').then(m => console.log(JSON.stringify(m.toJsonSchema(), null, 2)))"The schema is pinned to input mode — the shape an author writes, before defaults. An agent generates a candidate config against it; the validate step is the gate:
const cfg = parseConfig(candidate); // throws on structural errors
const errs = validateConfig(cfg); // [] = valid; else human-readable reasonsThis is the contract-as-test pattern: the agent proposes, the schema disposes. A config that parses and validates is guaranteed renderable.
The loop
Explore the source assets
Decide what is being chosen and gather raw material. For image specimens, record each asset's intrinsic pixel dimensions (the crop engine needs them) and find clean crop rectangles — think in a decile grid, each crop a normalized { x, y, w, h } of the intrinsic image. Collect the brand's colors, radii, and fonts for the theme.
node -e "const{imageSize}=require('image-size');console.log(imageSize(require('fs').readFileSync(process.argv[1])))" path/to/board.pngAuthor loupe.config.ts
Write a Config (or fill the JSON Schema). Each group is a decision; each option a choice; each option's specimen is one of the five kinds. Mark one option per group recommended. Add banned[], notes[], workflow[] — that prose flows into the brief. Validate before generating.
Generate the self-contained artifact
Run the generator from a directory that can resolve @lucentive-labs/loupe-generator — simplest is inside this monorepo, beside the examples.
tsx generate.ts # → dist/index.html + dist/assets/* (portable)generate() validates, copies assets, inlines JS + CSS, and rewrites asset URLs so the file works under file:// and any static host. Output is deterministic. See loupe-generator.
Screenshot-verify with Playwright
This is the step that makes it trustworthy. Write a verify.mjs that loads the artifact and asserts, mechanically:
- no console / page / local-asset errors;
- no broken crops — every
<img>hasnaturalWidth > 0; - tiles toggle — clicking a tile flips
aria-checkedon it and off its sibling; - preview + brief update —
[data-loupe-preview]and the[data-loupe-brief]textarea both change on a lock; - per-group
[data-loupe-clear],[data-loupe-reset],[data-loupe-recommend], and keyboard arrows behave; - capture desktop (1440×900) and mobile (390×844) with
reducedMotion: "reduce"andcolorScheme: "light"for determinism.
tsx verify.mjs # → screenshots/desktop.png + screenshots/mobile.pngIterate the crops against the screenshots
Open the screenshots. If a crop lands on the wrong detail or shows letterboxing, the rect or the intrinsic dims are wrong — nudge that option's { x, y, w, h } and re-run verify. Repeat until every tile frames its subject. The agent reads its own screenshots; this is a closed visual loop, not a guess. (See the crop model.)
Export the brief
The deterministic handoff. In the running artifact, Copy brief copies the markdown; both forms come from core:
tsx -e "Promise.all([
import('@lucentive-labs/loupe-core'),
import('@lucentive-labs/loupe-schema'),
import('./loupe.config.ts'),
]).then(([core, schema, m]) => {
const cfg = schema.parseConfig(m.config);
const sel = core.recommendedSelections(cfg);
const b = core.selectExportBrief(cfg, sel);
console.log(b.markdown);
console.log('\n---JSON---\n' + JSON.stringify(b.json, null, 2));
})"Hand the brief to the production pass as ground truth. Because preview and brief derive from the same selections, the thing the reviewer saw is exactly the thing the builder gets.
Why this is trustworthy
Three properties make the agent loop safe to run unattended:
- The contract is enforced. A config that does not parse and validate cannot render — the agent cannot ship a malformed lock.
- The decision is verified, not asserted. The screenshot step proves the tiles render and toggle and the crops are not broken, with real pixels and real DOM assertions.
- The handoff is deterministic. No timestamps, no absolute paths, config order preserved — so the brief diffs cleanly and an agent can assert on it byte-for-byte.
Safety by construction: motion presets and layoutMock plans are fixed enums, so an agent-authored config selects a feel or a structure but never injects raw CSS or scripts. All author text is escaped and image URLs are allowlisted. See loupe-core › Security.
Worked examples
The repo ships three real fixtures the skill points at:
brand-starter
The lightest, asset-free starter — palette / type / layoutMock / motion, with a swapped brand theme.
ias-control-plane
A real product-surface lock for IAS — signal cyan on ink, nine genuine decisions. See it in recipes.
human-today
The full photographic art-direction lab, using imageCrop over real boards.