# Emaki > Compact contract for AI agents painting watercolor in a browser the owner can watch. Static and browser-local: no remote paint API, model, account or cloud sync. The owner gives a subject; this file is the rest of the job: paint one watercolor where the owner can watch, accept a trial only after looking at its actual image, then deliver as in Finish. ## Start 1. Any browser works: the owner's open Emaki tab, a new window, or your own built-in browser. Prefer one the owner can see (not headless), so they can watch the painting form; if you can only run headless, send the owner an image after each stage. Open `/` or `/en/` and wait for `document.body.dataset.workspaceReady==="true"`. 2. `const S=window.emakiStudio; let o=S.observe({includeImage:false});` Require `o.writable && !o.busy`. One Emaki tab per browser profile can write. If `writable` is false, another Emaki tab in this browser holds the paper: work in that tab instead, or stop and ask the owner to close it, then reload. 3. Saved art lives in that browser. If `o.marks>0`, first `await S.execute({op:"snapshot"})`. Mutations require the current `expectedRevision`; after any error re-observe and fix the named cause. Never blindly retry. ## First painting ```js o = S.observe({includeImage:false}); if (o.marks > 0) await S.execute({op:"snapshot"}); o = await S.newDocument({aspectRatio:"3:4", expectedRevision:o.revision}); const scene = S.scene(); // keep this fixed header scene.title = "Evening sea"; scene.layers.push({id:"sky", intent:"Pale sky wash, then let it settle", marks:[ {type:"brush", points:[[.1,.2],[.5,.18],[.9,.2]], width:.08, color:"#6f9fd8", opacity:.35, water:.4, duration:1.2, pressure:[.6,1,.5]}, {type:"wait", seconds:5} ]}); const trial = await S.preview(scene,{expectedRevision:o.revision}); // Inspect the actual trial image. Accept only if it serves the aim. o = await S.accept(trial.candidateId,{expectedRevision:trial.baseRevision}); const files = await S.exportBundle(); // PNG + scene JSON const movie = await S.exportVideo(); // required process video ``` `preview` renders a candidate on the paper without changing accepted art. On `archiveWarning`, keep the tab open; see [recovery](workspace.md#persistence-and-editor). `accept` adopts the active candidate; `undo` returns one accepted step. `newDocument` makes a new sheet, not a resized painting. ## Scene essentials - Keep the header returned by `S.scene()`: output width 720, material grid width 640, seed 42, `watercolor-standard-v2` / `paper-brush-v5`. Choose only an aspect ratio: `1:1, 4:5, 3:4, 2:3, 9:16, 5:4, 4:3, 3:2, 16:9`. - v5 simulates paper, water, pigment and brush. Dark drying edges, blooms, granulation and dry-brush breaks happen from water and timing, not from settings. Read [material-v5.md](/sketch/skills/material-v5.md) before painting. - Layer: `{id,intent,marks}`. IDs are unique and stable. `intent` is the short truthful stage caption shown to the owner and in the video. At most 32 layers. - Brush: `{type:"brush",points,width,color,opacity,water?,duration?,pressure?,tip?,pigment?}`. Points are 2–256 normalized `[x,y]` centerline positions. Width is `0.0001..0.15` of paper width; color is `#rrggbb`; pressure is one `0.05..1` value per point. `opacity` is paint in the mix (0 clear water, 1 near tube paste); `water` is how charged the brush is (default 0.6). A faster stroke leaves less water. - `tip`: `round` (default), `flat`, `fan`, `liner`, `hake`, `mop`, `hog`, or `{preset,...}` to change `bristles`, `length`, `point`, `stiffness`, `capacity`, `spread`, `thickness`. `pigment`: a named paint or a mix, e.g. `[{name:"french-ultramarine",part:1},{name:"burnt-sienna",part:1}]`. - Drying: `{type:"wait",seconds:0.1..180}`; a wash loses its shine in about 3 minutes and is dry in about 18. Layer `environment:{drying:"hold"|"fast", tilt:0..60}` pauses the clock, uses a hair dryer (6× faster) or tilts the board. Brush durations + waits: at most 7200 material seconds. - Other tools: `spatter`, `salt`, `mask` (masking fluid) and `unmask`; see material-v5.md. No fills, shape tools, pasted images, post-filters or image-generation substitutes. White paint is not an eraser; lift with a damp brush. - Technique cards (`/sketch/skills/agent-index.json`) were tested on older paper: ideas carry over, numbers do not. ## Iterate without resending the full scene Use stable layer IDs. All operations below create an unaccepted preview: ```js await S.execute({op:"append_layer", layer:newLayer, expectedRevision:o.revision}); await S.execute({op:"replace_layer", layer:completeReplacement, expectedRevision:o.revision}); await S.execute({op:"remove_layer", layerId:"old-detail", expectedRevision:o.revision}); await S.execute({op:"preview_patch", expectedRevision:o.revision, patch:{ title:"Refined evening sea", replaceLayers:[completeReplacement], appendLayers:[newLayer], removeLayerIds:["old-detail"] }}); ``` Add `trialId` to patch a saved trial instead of the accepted scene. A patch may change only `title`, `appendLayers`, `replaceLayers`, and `removeLayerIds`; full validation still applies. Use full `S.preview(scene,...)` for canvas/material changes or broad rewrites. For long computation use `{op:"submit"}` jobs (see workspace.md). ## Inspect and decide - Screenshot `#studio-paper`, or read a saved trial crop with `S.execute({op:"image",trialId,crop:[x,y,w,h],maxWidth:720})`. Treat valid JSON and metrics as execution evidence, never as art-quality evidence. - Compare one hypothesis at a time. Record aim, what to preserve, change, visible observation and reason with `{op:"record",trialId,notes,decision:"kept"|"rejected"|"deferred"}`. No-change and defer are valid. - For an older candidate, call `{op:"branch",trialId,expectedRevision}` and accept the newly returned candidate ID. A later preview replaces the active candidate, but archived trials remain. - Without JavaScript access, use the page's scene form: see agent-reference.md. ## Finish and deliver 1. Export accepted files: `await S.exportBundle()` returns PNG/JSON artifact metadata and downloads both. `S.artifacts()` or `S.execute({op:"artifacts"})` lists stable re-download URLs for files made during this page session; the same links appear under the paper. 2. Export the required process video: `await S.exportVideo()`. It begins with the finished painting for 0.5 s, replays the brush process with layer-intent captions, and ends on the finished painting. It repaints the painting first (minutes on v5), then records in real time; keep the tab visible. For short tool timeouts use `S.execute({op:"video",wait:false})`, then poll `S.observe({includeImage:false}).video`. 3. Give the owner the real PNG, JSON and video plus a short note: aim, main stages, rejected trial/reason, and remaining limitation. Use the owner's language. 4. Ask whether the owner wants matching Gemini BGM added. Do not make music before explicit approval; if approved, follow [video.md](/sketch/skills/video.md). ## Load more only when needed - Full API, validation, compatibility and material detail: [agent-reference.md](/sketch/skills/agent-reference.md) - Trial, patch, artifact and WebMCP commands: [workspace.md](/sketch/skills/workspace.md) - Scene fields: [schema.json](/sketch/skills/schema.json) - Technique selector: [agent-index.json](/sketch/skills/agent-index.json) - Video details: [video.md](/sketch/skills/video.md)