01 · Overview
Two surfaces, one pipeline
In-app chat (electron/agent/sdkRunner.ts spawns the child) and the headless CLI (npm run agent) both run the same packages/autolens-agent/src/pluginRunner.ts — a Claude Agent SDK query() with the autolens-builder plugin, allowlisted ls-clad skills, and approved learnings injected, driving 25 in-process tools plus the live Lens Studio MCP catalog over http://localhost:<port>/mcp (port + token read from the project's .mcp.json).
ARCHITECTURE.md:11-49, pluginRunner.ts:223-235
Core principle: the model plans; code executes and verifies. Every step that can be deterministic is a tool, not a prompt.
Routing gate — decided before Phase 0
The pipeline drawn on this page is the Snapchat / Figma branch: Figma ingestion → 2D screen-space compiler → verify, building a Snap Lens in the camera. Specs/Spectacles is a different branch — it needs SIK/UI-Kit/world-space 3D and is built by CLAD's specs-experience-builder instead; this pipeline stops rather than mis-build it. Precedence for deciding which branch: user-stated → project .esproj targetPlatform → ask. Never guess.
lens-experience-builder.md:76-92
02 · Provenance
Who owns what — three layers
The single most clarifying frame for the owner: who built what. Three stacked layers, Lens Studio at the foundation, AutoLens on top.
Layer 3 — AutoLens
AutoLensThe moat built specifically for this tool: everything that makes AutoLens more than "an agent poking Lens Studio."
- 25 deterministic in-process tools (
packages/autolens-agent/src/tools/) - 9 own skills (
plugins/autolens-builder/skills/) - 6 own sub-agents (
plugins/autolens-builder/agents/) - codegen — the layout/wiring/behavior compiler (
src/lib/build/screenFlow/codegen.ts) - 18 Hard Rules (
lens-experience-builder.md)
codegen and the Hard Rules are AutoLens's own — invented here, for this pipeline.
packages/autolens-agent/src/tools/, plugins/autolens-builder/skills/, plugins/autolens-builder/agents/, src/lib/build/screenFlow/codegen.ts, lens-experience-builder.md
Layer 2 — CLAD (ls-clad)
CLAD
Snap's vendored agent-knowledge library — a read-only snapshot at plugins/clad/: ~58 skills + 9 agents covering both Snapchat lenses and Spectacles. AutoLens borrows only 8 of its skills — the LS-general craft it would otherwise re-derive. It never modifies CLAD. The rest of CLAD (the entire specs-* family, the specs-experience-builder agent) is the Spectacles branch this pipeline's routing gate deliberately hands off, not builds.
plugins/clad/skills/ (~58 dirs), plugins/clad/agents/, skillAllowlist.ts:46-55
Layer 1 — Lens Studio (Snap)
Lens StudioThe raw editor, reached over MCP. The ~21 mcp__lens-studio__* tools are Lens Studio's own API surface, exposed by the LS MCP server the user starts. AutoLens calls them over http://localhost:<port>/mcp but does not own or ship them. They do primitive operations — create a scene object, run editor JS, generate a texture, recompile TypeScript, capture a preview — and they fail silently ("MCP success ≠ done": a write can return ok and change nothing). That silent-failure surface is the entire reason Layer 3 exists.
pluginRunner.ts:220,223-235, lens-experience-builder.md:17-61
The key relationship: an AutoLens-own tool is a deterministic compiler/verifier that orchestrates many Lens Studio MCP calls into one checked operation. build_from_manifest compiles a plan into a single VirtualScene apply instead of ~300 imperative LS calls; verify_geometry reads LS's scene-graphql and auto-corrects anchors; wire_screen_flow runs codegen + binds @inputs + recompiles + verifies in one call. The skills (own + CLAD) are the knowledge the orchestrator reads; the Hard Rules are the discipline it follows; the AutoLens tools are the deterministic hands it acts with; the LS MCP catalog is the raw editor underneath.
ARCHITECTURE.md:33-49, pluginRunner.ts:207-235
03 · Pipeline
The build pipeline, phase by phase
Routing gate → Phase 0 Plan → Phase 1 Assets → Phase 2 Build + Wire → Phase 3 Self-audit → Phase 4 Verify → Learn. Click a phase to expand it. HR chips link to the Hard Rules section; skill chips are the domain knowledge loaded at that gate.
Routing gateDecide the platform once, before any scene work — never interleave branches
What
A single, committed decision: is this the Snapchat/Figma branch (this pipeline) or the Specs/Spectacles branch (a different pipeline, built by CLAD's specs-experience-builder)?
How
Precedence, checked in order: user-stated platform → project .esproj targetPlatform → ask. Never guess.
Why
This pipeline is a 2D screen-space compiler only. Forcing a Specs/Spectacles request (SIK/UI-Kit/world-space 3D) onto it produces a wrong result — the gate stops rather than mis-builds.
lens-experience-builder.md:76-92
Phase 0 — Plan onceBuildManifest + Lens Brief, before any scene work
What
A validated understanding of the experience (Lens Brief) + a concrete per-scene build plan (BuildManifest).
How
- 0a — scan_figma: structural scan
- 0b — capture_figma_storyboard + read the overview render with vision + plan_lens_from_storyboard (persists
.autolens/lens-brief.json; a plan-critique pass; unanswerable open questions PAUSE the run — they are not guessed) - 0.5 — deploy_template: MUST return
ok:truebefore any build; writestemplate-uuids.json - 0c — probe the scene via scene-graphql
- 0d — emit the BuildManifest JSON
Why
"Plan once, execute declaratively" is the whole reliability thesis — improvising the next MCP call is the 300-call anti-pattern this agent exists to kill.
lens-experience-builder.md:100-425
Phase 1 — Asset ingestionEvery texture/font the design needs, installed into the project
What
Every texture/font the design needs, installed into the LS project, returned as a textureMap.
How
materialize_assets with the manifest assets + fontFamilies (resolves fonts via FontSelector; omitting them ships text as font:None). apply_element_animation records entrance-animation hints into the Brief.
Why
Assets are the moat — everything comes from Figma or bundled product assets, never a generator (the one exception is the lens publish-icon).
lens-experience-builder.md:429-454
Phase 2 — VirtualScene execute + behavior wiringThe scene built (geometry) and the interactive behavior wired (scripts)
What
The scene built (geometry) and the interactive behavior wired (scripts).
How
- Per scene: build_figma_frame (the mandated Figma-frame builder — routes Safe/Full by geometry, remaps anchors, reuses logo, dedupes) — or build_from_manifest for residual hand-built layers — collapsing to ≤2 VirtualScene applies (Phase A create + Phase B
@inputwiring) - Step 2e — wire_screen_flow generates the behavior layer via codegen (named feature controllers + the hub, binds
@inputs) lens-script-authorfills the//TODO(agent)stubslens-verifierreviews them
Delegation is mandatory for 3+ scenes — spawn scene-builder per scene, sequentially.
Why
One declarative apply is diffable/idempotent/verifiable; hand-driving hundreds of calls is expensive and high-variance.
lens-experience-builder.md:458-508
Phase 3 — Self-audit + semantic gateProof the scene actually contains what the manifest said
What
Proof the scene actually contains what the manifest said.
How
A read-only scene-graphql grep (manifest ↔ scene, by name) + verify_semantics (checks meaning, not just existence — wrong-type rig @inputs, silent ts-input failures, missing layer masks, distorted aspect, dead textures).
Why
"The tool returned ok" is not "done" — something can exist but not mean what the wiring assumes; a live human caught exactly this class on the 07-01 run.
lens-experience-builder.md:512-558
Phase 4 — Verify loop (see-and-fix)Proof the scene looks right, is on the right camera, at the right depth, and behaves right
What
Proof the scene looks right, is on the right camera, at the right depth, and behaves right.
How
- verify_geometry — deterministic sizing (MANDATORY, run first, never by eye)
- verify_render_order — z-order
- verify_layer — camera-layer mask
- verify_lens_states — drive each state, capture, vision-diff vs Figma
- dispatch_verify_fixes — batch the mechanical fixes, escalate the rest
- verify_behavior — inject a gesture per interaction, read
stateNowback — proves the tap landed - capture_preview_sequence + verify_motion — grade the feel of transitions
The suite is keyed to the feature set — gate selection is not discretionary.
Why
Structure + stills both passed while the interaction was dead — nothing had actually tapped the lens; these gates close that.
lens-experience-builder.md:561-723
LearnDurable lessons captured at run-end and post-run, fed back into future builds
What
Durable lessons captured and fed back.
How
record_learning files a lesson at run-end (→ pending/); npm run harvest -- <archive> mines the run's NDJSON for the same signals; the owner approves/rejects in the Learning Hub; approved lessons inject into every future build prompt (cap 60). sweep_unused_assets is explicit housekeeping — never part of a build.
Why
The loop turns a live surprise into a permanent rule — but stale lessons mislead, so retirement tombstones them.
lens-experience-builder.md:727-729, learning.ts:20-41
04 · Data Shapes
What the agent sees — data shapes
The owner's question, answered literally: when the agent scans Figma, plans a lens, builds a scene, and wires it up, what JSON does it actually get back? Nine cards below walk down the pipeline in call order. Every example carries a real file:line type citation, plus a mandatory honesty badge: real fixture means the values come from an on-disk fixture/cache (mostly the Visa×FIFA Fan Type 1 scan and the asset-dedup texture UUIDs); constructed from type means the shape is real but the example values were built strictly from the TypeScript interface because no on-disk sample exists (no real lens-brief.json, facts.json, or BuildManifest exists anywhere in this tree). A constructed example is never presented as real.
plans/pipeline-explainer-site.md — Appendix H
scan_figmaThe lead card — structural scan of one Figma frame, real fixture values
What the agent does with this: reads each frames[].elements[].class to decide per element (bake PNG / reuse template logo / bundled hint / native Text) and uses anchorHint as the exact box the build will place — before writing the BuildManifest.
export interface FigmaScanElementItem {
nodeId: string; name: string;
bbox: {x: number; y: number; width: number; height: number};
class: string; //export-png | text-layer | separate-animated | separate-interactive
//| reuse-template-logo | use-bundled-hint | skip-* | container
exportable: boolean;
anchorHint: {left: number; right: number; bottom: number; top: number};
action: string;
text?: string; fontFamily?: string; fontSize?: number; textColor?: string;
renderOrder?: number; stretchMode?: number;
}
export interface FigmaScanFrame {
nodeId: string; name: string; page?: string;
lensMode?: 'selfie' | 'world' | 'common';
bbox: {width: number; height: number};
elements: FigmaScanElementItem[];
recommendations: string[]; nextSteps: string[];
}
export interface FigmaScan {
fileKey: string; frames: FigmaScanFrame[];
devNotes: {page: string; text: string}[];
detectedMode?: 'common' | 'selfie' | 'world';
flow?: FigmaProtoFlow;
}
src/lib/figma/scanTypes.ts:3-19,21-30,43-50 — tool wraps as {ok, scan, errors} at scanFigma.ts:202; class union at figmaApiImporter.ts:411
{ "ok": true, "scan": {
"fileKey": "EXAMPLE_FILE_KEY", "detectedMode": "selfie",
"frames": [ { "nodeId": "146:308", "name": "Fan Type 1", "bbox": {"width":334,"height":732},
"elements": [
{"nodeId":"146:311","name":"Type of Fan","class":"export-png","exportable":true,
"action":"Bake to PNG → ImageLayer","bbox":{"x":76,"y":334,"width":182,"height":44},
"anchorHint":{"left":-0.442,"right":0.4129,"bottom":-0.09,"top":0.03}},
{"nodeId":"146:314","name":"Catchphrase","class":"text-layer","exportable":false,
"action":"TEXT node → native Text","text":"You, my friend, are a","fontFamily":"Inter","fontSize":20,
"anchorHint":{"left":-0.76,"right":0.76,"bottom":0.10,"top":0.18}},
{"nodeId":"146:2","name":"Logo","class":"reuse-template-logo","exportable":false,
"action":"Reuse template Logo SO (do not export)"},
{"nodeId":"146:320","name":"Sparkle 1","class":"separate-animated","exportable":true,
"action":"Separate PNG, will animate"}
], "recommendations":[], "nextSteps":[] } ],
"devNotes": [], "persistentElementNames": ["Logo"] }, "errors": [] }
//… frames[] trimmed 11→1, elements trimmed to 4
Real: frame 146:308 "Fan Type 1" 334×732, element names/classes, and "Type of Fan"'s anchor (left -0.442 right 0.4129) + box are asserted verbatim in src/lib/figma/scanFigmaFrame.test.ts:118-136. Not real: the other elements' node ids/anchors are constructed from type.
capture_figma_storyboardThe whole-page render, for captions and flow the structural scan can't see
What the agent does with this: Reads overviewImagePath (the whole PAGE render — captions + flow arrows between frames the structural scan can't see).
return reply({
ok: true,
overviewImagePath,
frameRenders,
errors,
note: overviewImagePath
? 'overviewImagePath is the full page — Read it for captions/flow; frameRenders are per-screen.'
: 'No page overview resolved; frame renders only.',
});
packages/autolens-agent/src/tools/captureFigmaStoryboard.ts:119-127 (literal return shape)
{ "ok": true, "overviewImagePath": "figma-cache/EXAMPLE_FILE_KEY/renders/0_1.png",
"frameRenders": { "146:308": ".../renders/146_308.png", "146:98": ".../renders/146_98.png" },
"errors": [] }
plan_lens_from_storyboard → Lens BriefThe persisted understanding of the experience — what the lens does
What the agent does with this: shows summary, resolves openQuestions before Phase 2, persists this as the single source build_figma_frame/wire_screen_flow read.
export interface PlannerResult {
success: boolean;
brief?: LensBrief;
briefPath?: string;
summary?: string;
openQuestions?: string[];
openQuestionOptions?: string[][];
error?: string;
validationErrors?: string[];
partial?: boolean; //true when the run ended without plan_finalize succeeding
turns: number;
}
export interface LensBrief {
schemaVersion: 1;
lens: { name: string; genre: LensGenre; mode: 'selfie'|'world'|'common'; figmaFileKey: string; storyboardPage?: string };
features: LensBriefFeature[];
flow: { start: string; transitions: LensBriefTransition[] };
scoring?: LensBriefScoring;
interactions: LensBriefInteraction[];
hints?: LensBriefHint[];
openQuestions: string[]; //unknowns surfaced for the user — NEVER guessed silently
openQuestionOptions?: string[][];
resolvedDecisions?: LensBriefResolvedDecision[];
entranceAnimations?: LensBriefEntranceAnimation[];
}
packages/autolens-agent/src/planner/runPlannerAgent.ts:21-35 (PlannerResult); src/lib/agent/lensBrief/types.ts:164-201 (LensBrief; features :76-98, transitions :102-111, scoring :113-129, variantSets :65-74)
{ "schemaVersion":1, "lens":{"name":"Visa Fan Type","genre":"personality-quiz","mode":"selfie",
"figmaFileKey":"EXAMPLE_FILE_KEY"},
"features":[
{"name":"Question1","controller":"Question1Controller","kind":"family","layoutFrame":"146:98",
"questionFlow":{"model":"single-screen","textSwapOnly":true}},
{"name":"FanReveal","controller":"FanRevealController","kind":"family",
"frames":["146:308","146:360","146:409","146:462","146:513"],
"variantSets":[{"slot":"fan-type-display","variants":["fan_type_1","…","fan_type_5"],
"drivenBy":"scoreBucket","default":"fan_type_1"}]}],
"flow":{"start":"Question1","transitions":[{"from":"Question1","to":"Question2","trigger":"answerCommitted","transitionStyle":"fade"}]},
"scoring":{"model":"bit-index","outcomes":5,"surpriseChance":0.1},
"interactions":[{"feature":"Question1","kind":"tilt-to-select","inputModel":"both","feelTunables":{"tiltThreshold":0.35}}],
"openQuestions":["Should the fan type persist to the camera-roll caption?"] }
Populated with real Visa frame ids + its known 5-fan-type mechanic (types.ts:58-63) — no real lens-brief.json exists on disk anywhere in the tree.
BuildManifestThe per-scene build plan — how to construct the scene
What the agent does with this: hands this + textureMap to build_from_manifest, which compiles it into ONE VirtualScene apply (never hand-writes anchors/masks/payload).
export interface BuildManifest {
schemaVersion: typeof MANIFEST_SCHEMA_VERSION;
lensMode: 'selfie' | 'world' | 'common';
template: 'thearlab' | 'none';
assets: BuildManifestAsset[];
layers: BuildManifestLayer[];
groups?: BuildManifestGroup[];
scripts?: BuildManifestScript[];
wiring?: BuildManifestWiring[];
}
export interface BuildManifestAsset {
id: string; type: AssetType; source: AssetSource; name: string;
figmaNodeId?: string; prompt?: string; path?: string; preset?: string;
}
export interface BuildManifestLayer {
id: string; name: string; type: LayerType; anchor: AnchorRect;
frame: FrameSlot; cameraLayer: CameraSlot; groupId?: string; renderOrder?: number;
//…persistent/reuse/variantSlot/drivenBy/hint/animation fields trimmed
assetRef?: string; stretchMode?: number; tintColor?: string; opacity?: number;
flipX?: boolean; flipY?: boolean; expectedAspect?: number;
text?: string; fontSize?: number; fontFamily?: string; textColor?: string; textAlignment?: TextAlignment;
}
src/lib/agent/lensBrief/manifest.ts:170-179 (BuildManifest); :41-55 (BuildManifestAsset); :58-129 (BuildManifestLayer)
{ "schemaVersion":1, "lensMode":"selfie", "template":"thearlab",
"assets":[{"id":"q1_card","type":"texture","source":"figma","name":"Q1 Question Card","figmaNodeId":"146:120"}],
"layers":[
{"id":"question_card","name":"Q1 Question Card","type":"image","assetRef":"q1_card","frame":"safe",
"cameraLayer":"main","anchor":{"left":-0.8,"right":0.8,"bottom":0.2,"top":0.7},"stretchMode":2,"expectedAspect":1.4},
{"id":"catchphrase","name":"Catchphrase","type":"text","text":"You, my friend, are a","fontFamily":"Inter",
"fontSize":20,"textColor":"#ffffff","frame":"safe","cameraLayer":"main",
"anchor":{"left":-0.76,"right":0.76,"bottom":0.1,"top":0.18}}] }
Glossary: BuildManifest vs Lens Brief · Pipeline: Phase 0 (0d)
materialize_assetsEvery texture/font installed, returned as a textureMap
What the agent does with this: passes textureMap/fontCache to build_from_manifest and resolvedVariantSlots to wire_screen_flow.
return reply({
textureMap, fontCache, errors, installed: Object.keys(textureMap).length, total: rawAssets.length,
organized: moved.size, reused, resolvedVariantSlots, variantSetErrors,
});
packages/autolens-agent/src/tools/materializeAssets.ts:277-280 (literal return shape — wider than the old doc, also organized/resolvedVariantSlots/variantSetErrors)
{ "textureMap":{"q1_card":"06505b2e-8561-4074-b01e-515d43bc7833","q1_tilt_hint":"d6f0b7fe-548f-42bd-bf82-c381a62e12bf"},
"fontCache":{"Inter":"b1e0…-font-uuid"}, "errors":[], "installed":2,"total":2,"organized":2,"reused":0,
"resolvedVariantSlots":[{"slot":"fan-type-display","drivenBy":"scoreBucket",
"variantTextureUUIDs":["59eefac8-…","254a1c45-…"],"defaultIndex":0}], "variantSetErrors":[] }
textureMap UUIDs are real, read from .autolens/asset-dedup.json (e.g. Q1 Question Card → 06505b2e-8561-4074-b01e-515d43bc7833). The rest of the shape (fontCache/resolvedVariantSlots values) is illustrative.
build_from_manifestOne VirtualScene apply — idMapping is the id→UUID handoff
What the agent does with this: reads idMapping to target the right component UUID for follow-up writes, checks skippedAsExisting (dedup ran), forwards aspectChecks to verify_semantics.
return result({
ok: errors.length === 0,
applied: applied.applied ?? 0,
errors,
idMapping,
anchorsSet,
created: (instructions.create ?? []).length,
modified: Object.keys(instructions.modify ?? {}).length,
deleted: (instructions.delete ?? []).length,
skippedAsExisting,
aspectChecks,
validationWarnings: v.warnings,
//…flowBlind fields trimmed
});
//idMapping KEY forms: SceneObject "$temp:so_<layerId>" (applyInstructions.ts:101,183)
// component "$temp:so_<layerId>:<Type>#0" (manifest.ts:692)
packages/autolens-agent/src/tools/buildFromManifest.ts:284-297 (literal return shape); src/lib/build/applyInstructions.ts:101,183; src/lib/agent/lensBrief/manifest.ts:692
{ "ok":true,"applied":6,"errors":[],
"idMapping":{
"$temp:so_question_card":"a3f01c2e-…","$temp:so_question_card:ScreenTransform#0":"b7c92d40-…",
"$temp:so_question_card:Image#0":"c1d83e50-…","$temp:so_catchphrase":"d2e94f60-…",
"$temp:so_catchphrase:ScreenTransform#0":"e3fa5071-…"},
"anchorsSet":2,"created":2,"modified":0,"deleted":0,"skippedAsExisting":["Logo"],
"aspectChecks":[{"sceneObjectUUID":"a3f01c2e-…","expectedAspect":1.4}],"validationWarnings":[] }
build_figma_frame + facts.jsonThe Figma-frame keystone builder, and the append-only fact log it writes
What the agent does with this: applies suggestedAnimations via one apply_element_animation, chains nextSteps verifiers; a resumed run reads facts.json instead of recomputing.
//derivedFacts.ts — facts.json shape
export interface DerivedFact { key: string; value: unknown; at: string; }
interface FactsFile { schemaVersion: 1; facts: DerivedFact[]; }
//buildFigmaFrame.ts — literal return shape
return reply({
ok: errors.length === 0, results, errors, summary, log,
factsFile: FACTS_RELATIVE_PATH,
factsNote: `Resolved node ids + constants were appended to ${FACTS_RELATIVE_PATH}…`,
nextSteps: ['verify_geometry', 'verify_layer', 'verify_render_order'],
});
packages/autolens-agent/src/tools/derivedFacts.ts:9-18 (facts.json shape); buildFigmaFrame.ts:283-290 (return), :261-267 (facts keys written); FrameBuildResult/BuiltElement in figmaFrameBuilder.ts
{ "schemaVersion":1, "facts":[
{"key":"frame:Question 1","value":{"nodeId":"146:98","width":334,"height":732,"aspectK":0.8112},"at":"…"},
{"key":"container:Question 1","value":{"sceneObjectUUID":"f1a0…","name":"Question 1","parentRegion":"safe"},"at":"…"},
{"key":"feature-container:Question1","value":{"name":"Question 1","sceneObjectUUID":"f1a0…"},"at":"…"},
{"key":"element:Question 1:Q1 Question Card","value":{"figmaNodeId":"146:120",
"sceneObjectUUID":"a3f01c2e-…","textureUUID":"06505b2e-8561-4074-b01e-515d43bc7833"},"at":"…"}]}
aspectK 0.8112 and the frame dims are real, asserted in src/lib/figma/scanFigmaFrame.test.ts:136. The UUIDs and at timestamps are illustrative.
wire_screen_flow → resolvedViaThe per-binding provenance stamp — how each @input actually bound
What the agent does with this: reads resolvedVia to see exactly HOW each @input bound (subtree vs scene-wide global vs ambiguous/missing) before verify_lens_states.
export interface WiredFeatureReport {
feature: string; controller: string;
scriptAssetUUID: string | null; scriptComponentUUID: string | null;
boundInputs: string[]; unboundInputs: string[]; todos: string[];
//container → 'arg' | 'facts:feature' | 'facts:frame' | 'brief:frame' | 'feature-name';
//each element @input name → 'subtree' | 'global' | 'ambiguous' | 'missing'
resolvedVia: Record<string, string>;
}
export interface WireFlowReport {
success: boolean; features: WiredFeatureReport[];
hub: { updatedInPlace: boolean; scriptPath: string | null; refsWired: string[]; refsPending: string[]; todos: string[] };
recompile: { ran: boolean; note?: string };
verify: { ran: boolean; logErrors: string[] };
defects: string[]; nextSteps: string[];
}
src/lib/build/screenFlow/wire.ts:75-87 (WiredFeatureReport), :89-103 (WireFlowReport); resolvedVia values documented :83-85
{ "success":true, "features":[
{"feature":"Question1","controller":"Question1Controller","scriptComponentUUID":"7b2f…",
"boundInputs":["questionCard","answerLeft","answerRight","tiltHint"],"unboundInputs":[],"todos":[],
"resolvedVia":{"container":"facts:feature","questionCard":"subtree","answerLeft":"subtree","tiltHint":"global"}}],
"hub":{"updatedInPlace":true,"refsWired":["Question1Controller"]},"recompile":{"ran":true},"defects":[] }
Glossary: wire_screen_flow / wiring / resolvedVia · Pipeline: Phase 2 (2e)
verify_semantics / verify_geometry / verify_behaviorThe three gates that check meaning, sizing, and that a tap actually landed
What the agent does with this: types are exact; example values below are illustrative, not from a real run.
verify_semantics
interface SemanticDefect { severity: 'critical' | 'warning'; check: string; detail: string; }
//return: {ok, defects: SemanticDefect[]}
//real check names: inputTypeCheck | inputNamesNonEmpty | layerMask | aspectMismatch | textureLiveness | icHitArea
packages/autolens-agent/src/tools/verifySemantics.ts:17-21
{ "ok":false,"defects":[{"severity":"critical","check":"layerMask",
"detail":"\"tilt-hint\" has layers=1048576, expected 2 — present in the scene, invisible on the camera that should show it."}] }
verify_geometry
const pass = matched > 0 && matched >= requested && residuals.length === 0 && groupOffsetResults.length === 0;
//return: {checked, matched, requested, layerSource, corrected[], residuals[], unmatched[], groupOffsetDefects[], pass}
//pass is always false on matched:0 — a soft "0 matched" note used to look like a pass; it can't anymore
packages/autolens-agent/src/tools/verifyGeometry.ts:345-351 (return shape), :338 (pass invariant)
{ "checked":4,"matched":4,"requested":4,"layerSource":"facts",
"corrected":[{"name":"Q1 Question Card","builtWidthPct":52.0,"figmaWidthPct":63.0,"deltaWidthPct":11.0}],
"residuals":[],"unmatched":[],"groupOffsetDefects":[],"pass":false }
verify_behavior
interface InteractionResult { name: string; passed: boolean; observedState: string | null; detail: string; }
//return: {ok, results: InteractionResult[]}
packages/autolens-agent/src/tools/verifyBehavior.ts:45-49
{ "ok":true,"results":[{"name":"tilt left to pick answer","passed":true,
"observedState":"Question2","detail":"state advanced to expected 'Question2' after gesture"}] }
05 · Hard Rules
The 18 Hard Rules
HR = Hard Rule — each is a lesson compiled from a specific failed build; violating one is the cause of a "build came out broken."
Prevents: improvising the next MCP call (the 300-call anti-pattern).
lens-experience-builder.md:100
Prevents: per-layer MCP loops.
lens-experience-builder.md:104
Prevents: getting anchors/tint/flip/layer-masks wrong by hand.
lens-experience-builder.md:108
Prevents: the silent no-op of wiring the SceneObject UUID.
lens-experience-builder.md:112
Prevents: "tool returned ok" being mistaken for "scene is correct."
lens-experience-builder.md:116
Prevents: content parented under the wrong camera/mode (a name exists under common and selfie and world).
lens-experience-builder.md:120
Prevents: dying mid-verify with an unverified build (worse than a verified one with defects).
lens-experience-builder.md:124
Prevents: deleting the user's own work (a self-audit once deleted a text element the user had just placed).
lens-experience-builder.md:128
Prevents: shipped proportion errors vision judges miss (a 52%-vs-76% card).
lens-experience-builder.md:139
Prevents: one-giant-manifest builds and paying Opus rates for a mechanical loop.
lens-experience-builder.md:143
Prevents: building on a partial scaffold (empty parent UUIDs).
lens-experience-builder.md:165
Prevents: silently writing to the wrong project.
lens-experience-builder.md:176
Prevents: false "done" reports (assumed state ≠ verified state).
lens-experience-builder.md:185
Prevents: burning turns grepping AutoLens's own source (132 of 405 turns on one run).
lens-experience-builder.md:189
Prevents: the "Stream closed" teardown that kills the rest of the run.
lens-experience-builder.md:200
Prevents: repeated schema/API errors (13 GraphQL errors in one run, answered by an unopened skill).
lens-experience-builder.md:208
Prevents: the four motion defects (re-entrance on advance, slide-not-pulse feedback, visible full-bleed edges, >1.2s entrance).
lens-experience-builder.md:224
Prevents: shipping with zero visual-fidelity checks or an unverified final mutation.
lens-experience-builder.md:243
06 · Skills & Sub-agents
Skills & sub-agents
The orchestrator composes AutoLens's own maintained skills with a read-only, allowlisted subset of CLAD's vendored skills — never the whole ~58-skill CLAD set.
skillAllowlist.ts — OWN_SKILLS lines 24-34, BORROW_SKILLS lines 46-55
Loaded own skills (9) AutoLens
"The skills we wrote" — the loaded set, from OWN_SKILLS (skillAllowlist.ts:24-34). Each row: what it teaches (with one real rule) + where in the pipeline it loads.
Teaches: Figma→LS screen-space conversion + the deterministic build chain; anchor math, Safe/Full routing, stretchMode by role. Real rule: bleeds beyond ±1 are correct and must NOT be clamped; z-order is compiler-owned (leave renderOrder unset).
Used: Phase 0 storyboard understanding + every per-frame Figma build.
plugins/autolens-builder/skills/figma-import/SKILL.md:47, lens-experience-builder.md:812
Teaches: build verification: silent-failure self-checks, the LS 5.22 capture path, the pre-done checklist. Real rule: RunAndCollectLogsTool ALWAYS resets the lens, so capture first / logs last, never interleaved; report only what a readback confirmed this session.
Used: the OWN half of the HR16 preview gate + HR17 motion grading + pre-completion.
plugins/autolens-builder/skills/verify/SKILL.md:119,287
Teaches: MCP conventions for LS 5.22: legacy→modern shim, VirtualScene property-type limits, the valueType table, no-deploy-scripts. Real rule: scene-graphql composite fields need a subselection — a GraphQL validation error returns HTTP 200 with {errors} and NO data, so ?? [] silently blinds a gate.
Used: effectively always-on tool-surface reference, before any MCP build.
plugins/autolens-builder/skills/mcp-tools/SKILL.md:114
Teaches: declaring @input params in LS 5.x TS. Real rule: the decorator + class extends BaseScriptComponent model is MANDATORY — //@input comment form is JS-only; in a .ts file it's NOT parsed → inputNames:{} → every wire silently no-ops.
Used: before authoring any .ts script — Step 2e stub filling, hub/controller authoring.
plugins/autolens-builder/skills/ts-inputs/SKILL.md:14
Teaches: the two-tier model (ONE LensController hub + Scene Controllers via global.CustomTween). Real rule: this.api = {} throws (api is reserved on BaseScriptComponent) — expose enable()/disable()/reset(); choice buttons enter with popIn, never dropIn (HR17).
Used: before any animation work — apply_element_animation + wire_screen_flow.
plugins/autolens-builder/skills/animation-architecture/SKILL.md:33
Teaches: deploy/use the TheARLab v3 base (5-camera rig, 9 phases) via deploy_template; role-UUID reuse; bundled hint wiring. Real rule: hint playback (autoplay/fps) is NOT agent-controllable — no author-time API exists, "the correct number of attempts is zero"; a failed deploy is TERMINAL.
Used: the deploy-base phase + whenever wiring hints.
plugins/autolens-builder/skills/thearlab/SKILL.md:161
Teaches: quiz/question-flow lens: one persistent question screen (swap copy, don't rebuild), dual tap+tilt through one idempotent commit, bit-index scoring. Real rule: selectAnswer is idempotent — first commit (tap OR tilt) wins, second is a no-op.
Used: "the only genre skill loadable today," when building a sequential-question interaction.
plugins/autolens-builder/skills/quiz/SKILL.md:44
Teaches: asset management: two-level folders, dedup hygiene, lookup disambiguation, bundled-key imports. Real rule: there is NO automatic texture dedup — name-only dedup reused the wrong-colored box (2026-06-11), so textures import as "<name> <nodeId>".
Used: before installing packages / managing textures — materialize_assets, dedup, sweeps.
plugins/autolens-builder/skills/assets/SKILL.md:103
Teaches: the master hard-rules field notes: component-UUID targeting, layer masks, the 7-step build sequence, item 18's gate table. Real rule: SetLensStudioProperty target = component UUID, silent no-op on SO UUID.
Used: "load before ANY LS MCP work"; item 18 is the gate table HR16 mirrors.
plugins/autolens-builder/skills/lens-field-notes/SKILL.md:37
Excluded own skills (6 — on disk, not loaded) AutoLens
Present in plugins/autolens-builder/skills/ but omitted from OWN_SKILLS — the SDK's skills filter is include-only, so exclusion is by omission, not deletion.
Exclusion reason: LS-general duplicate → BORROW ls-clad:materials.
Teaches: material/render-order patterns. Real rule: never assign mainMaterial after CreateLensStudioComponent('Image') (silent no-op → invisible) — use CreateComponentFromPresetTool.
skillAllowlist.ts:17, plugins/autolens-builder/skills/materials/SKILL.md:16
Exclusion reason: LS-general duplicate → BORROW ls-clad:scene-construction.
Teaches: scene build/modify orchestration. Real rule: an MCP-created camera inherits near=1 which clips all screen content — must be near=-1 (A/B-proven live 2026-07-05).
plugins/autolens-builder/skills/scene-construction/SKILL.md:51
Exclusion reason: LS-general duplicate → BORROW ls-clad:editor-api/lens-api + own ts-inputs.
Teaches: runtime scripting conventions. Real rule: never re-InstallLensStudioPackage a script — it breaks every @input binding; write+save+RecompileTypeScriptTool is the only path that preserves bindings.
plugins/autolens-builder/skills/scripting/SKILL.md:243
Exclusion reason: dead-template, pending template-library.
Teaches: a randomizer-template build. Rides the .js + InstallLensStudioPackage pipeline that's dead-by-API on LS 5.22.
skillAllowlist.ts:18 — orchestrator says do NOT load
Exclusion reason: dead-template.
Teaches: a photo-booth-template build. Real rule: array @inputs (flashMats/rts) must be written as the FULL array in one call — .N element writes fail silently.
plugins/autolens-builder/skills/photo-booth/SKILL.md:33
Exclusion reason: maintenance/cron skill, dropped 2026-07-12 — wrong surface for the build agent; stays on disk, user-invocable.
Teaches: the learning-hub eval/approve loop. Real rule: the eval is the fixed point, and it NEVER merges — the PR is the human review gate.
skillAllowlist.ts:19-21
Notes: loaded=9 vs on-disk=15; the excluded 6 are dropped by omission, not deletion. The orchestrator's unqualified /scene-construction and /materials references resolve to the CLAD borrow, not the excluded own copies (same domain, different maintained source). lens-field-notes (loaded) still points at /self-maintenance (excluded) — fine for a human run, dead for the build agent. CLAD itself is ~58 skills; AutoLens borrows 8.
Borrowed ls-clad skills (8) CLAD
Material creation/config/assignment; Editor/GraphQL-vs-Runtime-API distinction.
Custom .graphVfx particle graphs.
LS TypeScript runtime scripting (StudioLib): lifecycle, decorators, touch.
The ExecuteEditorCode contract + Editor API paradigms.
Orchestrator for scene build/modify; the preset-first workflow; indexes domain skills.
Triage router for LS runtime bugs (symptom → specialist).
LS log format, severity prefixes, reading RunAndCollectLogsTool.
Snapchat in-camera Lens setup: camera tracking mode, preview via PreviewPanelTool.
skillAllowlist.ts:47-54 (one line per name)
Footguns
15 skill dirs exist on disk but only 9 load — the allowlist excludes materials/scene-construction/scripting (LS-general duplicates, replaced by the CLAD-borrow versions above), photo-booth/randomizer (dead-template), and self-maintenance (dropped 2026-07-12). So materials/scene-construction load from plugins/clad/, not the own dir. Separately, the borrowed vfx-graph skill's stated "required reading," shader-graph, is not allowlisted — a dangling reference. CLAD itself is much larger (~58 skills) than the 8 AutoLens borrows.
skillAllowlist.ts:16-23
Sub-agents (6) AutoLens
The Opus orchestrator itself. Runs the four-phase pipeline; delegates the loops below.
plugins/autolens-builder/agents/lens-experience-builder.md
Cheaper per-scene executor. Runs the mechanical build→verify→fix loop for ONE scene. Mandatory delegation for 3+-scene storyboards, spawned sequentially.
plugins/autolens-builder/agents/scene-builder.md
Fills wire_screen_flow's //TODO(agent) domain-trigger stubs + small leaf scripts; does not freehand whole controllers.
plugins/autolens-builder/agents/lens-script-author.md
Read-only post-build structural verifier + the Step-2e stub-fidelity review gate.
plugins/autolens-builder/agents/lens-verifier.md
Context shield for heavy ExecuteEditorCode work; landing spot for a flagged scene-builder escalation.
plugins/autolens-builder/agents/editor-api-specialist.md
Deep Figma analysis across many frames; assembles a BuildManifest fragment.
plugins/autolens-builder/agents/figma-importer.md
07 · Tools
Tools — 25 in-process + 21 Lens Studio MCP
The 25 in-process tools are AutoLens's own deterministic code the model can't skip; the 21 MCP tools are Lens Studio's own live editor surface — an AutoLens tool orchestrates many MCP calls into one verified operation (see Provenance).
25 in-process tools AutoLens
Plan (4)
plan_lens_from_storyboard | scan Figma → validated Lens Brief | AutoLens |
scan_figma | pure structured FigmaScan | AutoLens |
capture_figma_storyboard | render frames to PNG + surface captions/flow | AutoLens |
apply_element_animation | record entrance-animation hints into the Brief; no scene write | AutoLens |
Asset ingestion (1)
materialize_assets | install assets + fonts, return textureMap | AutoLens |
Build (4)
deploy_template | deploy TheARLab scaffold → roleUUIDs / template-uuids.json | AutoLens |
build_from_manifest | compile a BuildManifest → ONE VirtualScene apply, owns all payload details | AutoLens |
build_figma_frame | Figma-frame keystone: scan+build in one call, Safe/Full routing, dedup | AutoLens |
ensure_rig | idempotently create/locate tracking-rig objects | AutoLens |
Wire (2)
wire_screen_flow | Phase-2 flow wiring: generate + bind controllers + recompile + verify | AutoLens |
wire_script_inputs | set named @input values on one ScriptComponent | AutoLens |
Verify (12)
verify_lens_states | drive states, capture, vision-diff | AutoLens |
verify_semantics | LLM-free meaning check | AutoLens |
verify_behavior | inject gesture → read stateNow | AutoLens |
verify_motion | vision-grade transition feel | AutoLens |
verify_geometry | deterministic sizing + auto-correct | AutoLens |
verify_render_order | deterministic z-order | AutoLens |
verify_layer | deterministic camera-layer mask | AutoLens |
verify_scene_visual | per-scene pixel diff vs Figma | AutoLens |
capture_preview | one side-effect-free snapshot | AutoLens |
capture_preview_sequence | timestamped frame series | AutoLens |
set_preview_source | switch preview input media | AutoLens |
dispatch_verify_fixes | batch mechanical fixes, escalate the rest | AutoLens |
Learn / housekeeping (2)
record_learning | file a durable lesson | AutoLens |
sweep_unused_assets | explicit orphan cleanup, dry-run by default | AutoLens |
packages/autolens-agent/src/pluginRunner.ts:207-213
21 Lens Studio MCP tools Lens Studio
External, over HTTP; namespace mcp__lens-studio__*; declared in the orchestrator frontmatter. Every tool below is Lens Studio's own — badged per row.
Scene/asset authoring (5)
VirtualScene | Lens Studio |
scene-graphql | Lens Studio |
asset-graphql | Lens Studio |
ExecuteEditorCode | Lens Studio |
GetBoundingBox | Lens Studio |
Scripting (4)
RecompileTypeScriptTool | Lens Studio |
InstallLensStudioPackage | Lens Studio |
ListInstalledPackagesTool | Lens Studio |
ListAllPanels | Lens Studio |
Asset generation/search (7)
GenerateTexture | Lens Studio |
FontSelector | Lens Studio |
ConvertSvgToTexture | Lens Studio |
GenerateFaceMaskTexture | Lens Studio |
GenerateLensIcon | Lens Studio |
SearchLensStudioAssetLibrary | Lens Studio |
QueryLensStudioKnowledgeBase | Lens Studio |
Preview/runtime (5)
RunAndCollectLogsTool | Lens Studio |
CaptureRuntimeViewTool | Lens Studio |
CapturePanelScreenshotTool | Lens Studio |
InjectPreviewGesture | Lens Studio |
QueryRuntimeSceneTool | Lens Studio |
lens-experience-builder.md:17-37
Plus, off the declared list
PreviewPanelTool — the side-effect-free screenshot path (~5.9fps, ~170ms/call, no side effects), preferred over CapturePanelScreenshotTool which clicks the panel and taps the lens mid-verification.
plugins/autolens-builder/skills/verify/SKILL.md:127
08 · Glossary
Glossary (11 terms)
Plain-English definitions. Use your browser's Ctrl-F / Cmd-F — everything here is always expanded, no custom search box.
codegen
The pure string-builder that turns a confirmed Lens Brief into house-style TypeScript feature-controller scripts + the regenerated LensController hub (no MCP calls, just source).
Matters because: it OWNS the motion law — the single place deciding how every element enters a screen (which helper, in what order, with what timing) and enforcing the owner's playtest "LAWs" (elements primed hidden; completion-chained order; static art never animates; full-bleed bitmaps never scale; whole entrance chain compresses to ≤1.2s). The "motion law" is this entrance-choreography grammar, not physics.
src/lib/build/screenFlow/codegen.ts:1,96-101,335-357,420-497
wire_screen_flow / wiring / resolvedVia
After controller scripts compile, wiring binds each script's @input fields to the actual scene (container @input → container SceneObject; element @input → element SceneObject UUID; content-text @input → Text component UUID) via setProperty. wire_screen_flow does the whole flow in one call; wire_script_inputs is the single-script version. resolvedVia is the per-binding provenance stamp recording HOW each input resolved (arg/facts:feature/facts:frame/brief:frame/feature-name; per element subtree/global/ambiguous/missing).
Matters because: an unbound @input = a controller referencing nothing = a silently dead screen. The log shows exactly which path landed each binding and which were left unbound rather than guessed.
src/lib/build/screenFlow/wire.ts:1,83-87,385-446
facts.json
.autolens/facts.json — an append-only log of things a build already figured out (resolved Figma node ids, computed constants, per-frame/element scene UUIDs) so a later stage reads them instead of recomputing blind after the process that held them died. build_figma_frame writes frame:*, container:*, feature-container:*, element:*.
Matters because: the builder names containers after the Figma FRAME, not the feature — without the container→feature map, wire_screen_flow guesses the feature name and misses every container (the 1316 wiring cascade).
packages/autolens-agent/src/tools/derivedFacts.ts:1, buildFigmaFrame.ts:261
stateNow
A public read-only @input string on the generated hub mirroring the current flow state, rewritten on every setState.
Matters because: the real field (currentState) is a private TS field LS never exposes at runtime — a machine verifier can't read it; verify_behavior reads stateNow back to assert the flow advanced. Without it the behavior gate has nothing to read.
codegen.ts:1216,1278, verifyBehavior.ts:27-33
template-uuids.json handshake
The single deploy→build handoff: deploy_template writes {TheARLab role → LS asset UUID} to .autolens/template-uuids.json; build_figma_frame/build_from_manifest read it back. Written atomically (temp + rename) so a crash can't leave a truncated file a downstream JSON.parse silently swallows into "no UUIDs."
Matters because: this was the 5-of-7-run "No template UUIDs available" failure.
packages/autolens-agent/src/tools/templateUUIDsStore.ts:1,27-44
BuildManifest vs Lens Brief
Two artifacts at two altitudes. Lens Brief (lens-brief.json, from the storyboard planner) = the understanding of the experience (features, flow, transitions, scoring): WHAT the lens does. BuildManifest (Phase 0, per scene) = the build plan (layers, anchors, assets, camera layers, render orders): HOW to construct that scene.
Matters because: codegen consumes the Brief; build_from_manifest consumes the manifest — mixing up which artifact answers which question is a common confusion.
src/lib/agent/lensBrief/manifest.ts:1-4, types.ts:16-102
VirtualScene apply ("one apply")
LS 5.22's declarative batch write: hand it every object/component/material as one payload, get back an idMapping of your ids → real UUIDs. "One apply" = build an entire scene in a single call instead of ~50-300 imperative calls (now dead-by-API/HTTP-501 on 5.22 anyway).
Matters because: the LLM never hand-writes anchors/tint/flip/masks — the compiler emits them deterministically.
buildFromManifest.ts:1-6, deployCore.ts:1-7, figmaFrameBuilder.ts:4-9
ExecuteEditorCode (EEC) + the component-UUID trap
EEC runs arbitrary JS against the LS editor API (bulk reads, scene walks, atomic multi-step mutations GraphQL can't express). The trap: layout/style props (anchor, stretchMode, text, material, an @input) must target the component UUID, NOT the SceneObject UUID — targeting the SO UUID returns success but is a silent no-op.
Matters because: "MCP success ≠ done" — this is the single most common way a write silently no-ops.
editor-api-specialist.md:19-33, scene-construction/SKILL.md:147
ortho camera / layer mask
A 2D Orthographic Camera draws screen-space UI; each visual carries a layer mask and a camera only renders layers its mask includes. Captured UI → Main Ortho 1048576; preview-only hints → Live Only Ortho 2.
Matters because: a UI object left on its default layer=1 is drawn by NO camera and renders nothing — no log, no error — so the deploy is silently invisible.
scene-construction/SKILL.md:36,40-43, figmaFrameBuilder.ts:41-42
harvest / learnings / tombstone
A learning = a durable reusable lesson under .autolens/learned/{pending,approved,rejected}/. harvest deterministically mines a run's NDJSON into draft candidates; record_learning files one live; both land in pending/; a human approves/rejects; approved lessons inject into every future prompt (cap 60). A tombstone = a rejected/<id>.json whose presence means "never re-propose this id."
Matters because: retire-superseded.mts mass-moved 51 approved → 18 — stale lessons mislead if never retired.
packages/autolens-agent/src/learning.ts:1-4,20-41, recordLearning.ts:1-10, learning/harvestRun.ts:1-2, electron/learning/learnedStore.ts:14, scripts/learning/retire-superseded.mts:1-3
the verify_* gates
Matters because: "the tool returned ok" is not "done" — each gate below checks a different way a build can look right and not be right.
| Gate | Checks | Mode | File |
|---|---|---|---|
verify_semantics | object exists AND means what the wiring assumes (rig type, ts-input, mask, aspect, dead texture) | deterministic (no LLM) | verifySemantics.ts:1-5 |
verify_geometry | each layer's live anchor box matches the aspect-normalized suggestedAnchor; auto-fixes | deterministic | verifyGeometry.ts:1-6 |
verify_scene_visual | per-scene, pre-behavior: isolate scene, capture, vision-diff vs Figma | vision (Sonnet gate) | verifySceneVisual.ts:1-5 |
verify_behavior | inject one gesture per interaction, settle, read stateNow back, assert advance | deterministic probe | verifyBehavior.ts:1-5 |
verify_motion | grade a frame time-series for feel (continuity/easing/z-order/flicker/timing) | vision (stronger model) | verifyMotion.ts:1-9 |
verify_layer | which camera draws each visual (Main Ortho vs Live Ortho); fixes invisible case | deterministic | verifyLayer.ts:1-4 |
verify_render_order | live renderOrder matches the role-resolved z-order; fixes drift | deterministic | verifyRenderOrder.ts:1-4 |
verify_lens_states | drive each state's debugState, capture, grep logs, vision-diff vs Figma | hybrid | verifyLensStatesTool.ts:1-4 |
09 · Failure Map
Why tests go nowhere — honest framing
Builds run long and stall because tools break silently, not because the model hallucinates. A 2026-07-12 hallucination audit of a live run refuted the "hallucinating too much" theory — 1 confident-wrong-acted-on claim in ~87 checked assertions, zero fabricated identifiers, zero shipped defects from hallucination. Cross-run forensics (7 archives) found build phases are near-constant (~60-90 turns); all the variance lives in the post-build tail (302 tail-turns worst vs 4 best; ~4× cost spread).
The failure classes below each have a fix implemented in the reliability wave (commit ef5d693) and hardened by a follow-up Opus review fleet (9b3b13d — 21 agents, 4 more sub-defects fixed, 3 deferred with evidence), reviewed GO — but not yet live-validated (test-18 is the validator). Read these as diagnosed + fix landed + review-hardened, awaiting live validation — never "solved."
| Symptom seen in tests | Root cause | Fix that landed | Status |
|---|---|---|---|
| "No template UUIDs available" — deploy looked fine but the build had no parents | deploy→build handshake drift/corruption; triplicated read logic + non-atomic write; failed 5/7 runs | atomic canonical template-uuids.json handoff (see Glossary: template-uuids.json handshake) |
ef5d693 + 9b3b13d, awaiting test-18 |
| ~148 turns of hand-wiring recovery; "Input elSelfieFeed was not provided" | wire_screen_flow failed 100% of calls (5/5 across 3 runs): builder names containers by Figma frame, wirer searched by feature name; no scene-root path for shared elements |
facts.json container→feature map + UUID-first resolution + whole-graph fallback + resolvedVia provenance (see Glossary); HR-driven | ef5d693 + 9b3b13d, awaiting test-18 |
~100-turn "tap marathon" — every tap read observedState:"" |
the hub kept currentState private → verify_behavior structurally blind → false-fail on every transition |
hub exposes readable stateNow mirror; verify_behavior probes it first (see Glossary: stateNow) |
ef5d693 + 9b3b13d, awaiting test-18 |
| Downstream taps swallowed | Intro's armTap on a scene-root full-screen object survived disable() (no IC teardown in codegen) |
disable() disarms what armTap armed |
ef5d693 + 9b3b13d, awaiting test-18 |
2 of 7 runs shipped geometry-unverified; gates noted matched:0 instead of failing |
soft gates: verify_geometry returned "NOTHING was verified" as a note; verify_semantics false-RED on the {UUID,name} reference form |
gates hard-fail (matched:0 ⇒ pass:false); auto-load facts.json; parse {UUID,name} |
ef5d693 + 9b3b13d, awaiting test-18 |
| 21/37 EEC calls in one run were pure API probes; three incompatible scene-walk idioms | no canonical editor-JS snippet → the agent re-derives the editor API by introspection | one pinned ExecuteEditorCode idiom in mcp-tools (HR14/HR16) |
ef5d693 + 9b3b13d, awaiting test-18 |
| ~4.2k tokens/build of injected learnings, ~65% dead or misleading | learnings store went NET-NEGATIVE (51 approved = 16 keep / 20 superseded by code / 7 merge / 7 demote / 1 contradicts); no retirement trigger | mechanical retirement → 18 approved / 43 tombstoned (see Glossary: harvest / learnings / tombstone) | ef5d693, done (store state) |