WootBuild.

Studio/Doctrine/How we work

docs/13-WORKFLOW.md

13 — Workflow

The working agreement: how a session runs, how a client is onboarded, how work moves from a client's ask to a delivered file, and who approves what.

00-CHARTER.md defines the vocabulary and the non-negotiables. This file defines the procedure. Where they disagree, the charter wins.


The first images of a lane carry the most weight

Justin, 2026-08-18: "the first images that are created for any client should come under the most scrutiny, given that they do set the tone stylistically for the rest of the site or app."

This is now a rule, not a preference, and it has a mechanism.

The first accepted pieces in a lane do not merely ship — they become exemplars/, which the compiler reads as canon on every subsequent run. An early yes propagates silently into hundreds of later assets, and unlike a bad individual render it never announces itself. The cost of being wrong is asymmetric, so the scrutiny should be too.

What follows from it:

  1. A lane's look is chosen from options, not accepted from the first attempt. The forge produces two or three genuinely different directions, rendered at the size the lane is actually judged at, and a human picks one. Presenting one good option beside two broken ones is not a choice — if the alternatives fail the stated non-negotiables, say so and regenerate rather than staging a decision that has already been made.
  2. Nothing enters exemplars/ without a human verdict. Not during the forge, not as a convenience. Seeding exemplars by hand happened on 2026-08-18 and was wrong: it put work in the approved gallery that nobody had approved.
  3. A style stays draft until its direction is chosen. active means a human said yes to the look, not that the pipeline produced something.
  4. Spend more per image early. Three plate attempts to get a usable comparison is cheap against eighteen collectibles rendered in the wrong register — and far cheaper than a whole catalogue that has to be redone.

1. Naming, settled

Two words caused confusion and are now pinned.

"Project" is never used for a client. WootBuild lives inside a Claude project, each client is a Claude project, and the word therefore points at three things at once. The studio word is client. clients/<id>/client.yaml carries root:, which is the path to that Claude project — the link is explicit, so the duplication is a join key, not a redundancy.

"Category" is a lane. A lane is a distinct visual track inside one client: docade's badge work and its prize work are two lanes. Lanes are where style, spec, autonomy, and delivery path all get decided, which is exactly the level the word "category" was reaching for.

Two terms are added to the charter vernacular by this document. Nothing else changes, and no synonyms are introduced — these are table names.

TermMeaning
RequestWhat a client asks for, filed through the bridge (§5) before WootBuild has costed or specced it. Free text, a lane, a count, a deadline. Not yet a brief.
House styleA parent style sheet that a client's lanes inherit from, carrying what is common across the whole client. Lanes override with thin deltas.

The distinction between a request and a brief is the one that earns its keep: a request is the client's words, and it can be vague, mispriced, or impossible. A brief is WootBuild's compiled, specced, priced version — the thing a run executes. Collapsing them would put an unreviewed, unestimated ask directly in front of a paid model, which the second non-negotiable forbids.

The hierarchy, end to end

client            docade                 a Claude project that consumes imagery
└─ lane           avatar                 a visual track; the unit of style + spec
   ├─ style       docade-house@1         inherited, versioned, immutable once used
   │  └─ delta    docade-avatar@1        thin per-lane override
   ├─ spec        avatar-tile            512×512 png transparent — mechanical only
   └─ requests    "60 kid avatars"       from the client, via the bridge
      └─ brief    compiled + priced      → run → candidates → verdicts → assets
                                         → delivery back into the client repo

2. House styles, and why lanes now inherit

The charter says lanes do not share style sheets. That holds for salon-structure, whose diagram and human lanes share nothing but a client id. It breaks for docade, whose avatars, prizes, and badges must read as one illustrator's work while differing in framing, sizing, and subject matter.

Forging three unrelated style sheets for one visual world is how drift starts: three independent probe→converge cycles, three sets of lessons, and no mechanism that keeps them agreeing a month later.

So a client may declare a house style, and lanes inherit it:

house_style: docade-house@1

lanes:
  avatar:
    style: docade-avatar@1     # inherits docade-house@1, overrides only deltas

Rules, so inheritance does not become a second source of drift:

  1. A delta may narrow, never contradict. A lane may add subject framing or tighten a constraint. It may not restate the palette or swap the model binding — that is a different house, and should be one.
  2. Immutability composes. Editing a used house style forks a new version; inheriting lanes stay pinned to the version they were built against and are migrated deliberately, never implicitly.
  3. Lessons flow up, not down. A lesson learned in one lane is proposed to the house style, where a human confirms it and every lane inherits it. This is the whole point of the mechanism.
  4. A house style is optional. salon-structure will not have one, and forcing it to would be worse than the problem being solved.

3. Client onboarding

Five stages and a half. Stages 1–3 complete before a single image is generated — that ordering is the whole point, because every expensive mistake in this business is made by producing before deciding what the thing should look like.

Stage 4.5 is the exception that proves it: a deliberate sandbox where images are generated, cheaply, precisely so that nothing is locked in ignorance of what the models actually do.

Stage 0 — The working agreement

A gate, and what advances it is the client's own words. WootBuild guides this; it does not hand over a paragraph and call it agreed. A client who writes it themselves has adopted it. A client who pastes ours has filed it.

The generated contract page (clients/<id>/bridge.md, from lib/bridge.mjs) carries a draft. It is a starting point for their version, not the version.

This stage exists because RecHero wrote it before we asked. Client #2, on their own initiative, produced a working agreement sharper than the one WootBuild generates — and Justin's instruction was to make it part of onboarding for everyone. Most of what follows is theirs, and it is better than what it replaced.

The division of labour, written sharply

The client owns the requirements. WootBuild owns the execution. RecHero's formulation, and the useful half is the second sentence:

A deliverable that misses because our brief was vague is our failure.

That makes writing a spec precise enough to be missed the client's actual work, rather than something they can wave at. It also earns WootBuild the right to be held to the other half without argument.

The circumvention list

The failure mode arrives quietly, dressed as pragmatism. Each of these feels efficient on its own:

Their sum is a studio that never gets exercised, and a product whose visuals drift back to whatever was fastest — which is the exact outcome commissioning a studio was meant to avoid. Naming them individually is the point: as a slogan ("don't circumvent the studio") the rule does not survive contact with a deadline.

The challenge list

The obligations that run the other way, and they are the more demanding half:

Placeholders — the honest version, not a ban

The UI has to be built against something. So a placeholder is allowed, and:

  1. must be visibly a placeholder
  2. must be logged in the request drop-box
  3. is never shipped to an end user

An unlogged placeholder is circumvention with extra steps.

And what is not catalogue art

The agreement is guidance, not a gate on every image. A diagram, a chart, an internal mock, a throwaway — made in the client's own repo, and authored SVG usually beats a diffusion model for anything carrying real text. Routing a two-minute job through a studio is worse than doing it locally. What must not happen is shipping product art made by handing a screenshot to an image model.

Early clients carry a responsibility

Being client #2 is a responsibility, not a perk — RecHero's framing, recorded because the instinct when a process is rough is to quietly work around it. docade came in midstream and taught the studio a great deal; the process is still refining. Friction hit at intake is data the studio needs. A client who absorbs it silently is withholding the most valuable thing they have.

Stage 1 — Understand what the assets do

Not what they look like. Where they appear in the product, what job they perform, and what they sit beside. Appearance decisions fall out of function, and a lane scoped without it produces art that is individually attractive and collectively useless.

docade's task icons are the worked example: 32 glyphs at 24px whose real requirement is that laundry stays distinguishable from fold-clothes in a list, at thumbnail size, with no colour. That requirement is invisible from the subject line and obvious from the placement.

Stage 2 — Volume profile, and the capability gap

QuestionWhy it decides something
Immediate volumeSets the first batch, the budget, and the review cadence.
Ongoing volumeDecides training vs reference conditioning. A one-off batch of 30 never justifies a LoRA; 150/year does.
Phase orderWhat ships first is rarely what is most interesting to make.
What does the best version require?The required question. See below.

This stage asks what the ideal deliverable is, not what we can currently make. Per lane: given what the assets have to do (Stage 1), what is the best possible artifact for that job? Decide that first, with the roster ignored.

Only then compare it to what WootBuild can produce today — checking 14-CAPABILITY-RADAR.md, which records what the studio could adopt and what would trigger it — and write the difference down as a staffing gap — the capability, why the work needs it, a rough cost, and who can close it. A gap is a hiring decision, never a reason to narrow the ask. It is also never left implicit: an unnamed gap becomes a category that quietly ships short while every dashboard reads green.

And challenge the list itself. Stage 2 is where the studio asks whether the requested work is the right work: is it sequenced by value, does the plan contradict itself, and is any of it unnecessary? Findings go in clients/<id>/advisory-<n>.md before production starts, with the client's own evidence cited. See 00-CHARTER.md § The studio advises. Rendering a bad list beautifully costs the same as rendering a good one.

Worked example, docade crane: 14 pieces the product uses as the reveal moment of its core loop. The best artifact is animation, so the gap is animation production — not "deliver stills because that satisfies check-assets." Delivering the still is how a studio ships a hollow category and calls it done.

Stage 3 — Art direction, before anything is generated

The deliverable is a visual mission — templates/visual-mission.md, stored at clients/<id>/visual-mission.md. It answers "what should this look like, and why that" for the whole client, and it carries both halves:

This document governs the house style (§2). Lanes inherit from it, and a lane that cannot be derived from it is a signal the mission is wrong or incomplete — not a licence to improvise.

Gate: a human approves the visual mission before any lane is forged. It is cheap to argue about a document and expensive to argue about 200 images.

Stage 4 — Configure the client

#StepOutput
1art client new <id> --root <path>clients/<id>/client.yaml
2Lane inventory — group by production method, not by the client's delivery categorieslanes: keys
3House style — derived from the visual mission (§2)house_style: or nothing
4Mandate — how much latitude do we actually have? Declared, never assumedmandate:
5Budget — per-client monthly capmonth_ceiling:
6Delivery constraints — live repo? branch-only? filename foreign keys?client.yaml comments
7Request source — a manifest the client already maintains, or the bridge (§5)recorded

Lanes group by how a thing is made, not by where it lands. docade delivers into 19 directories but has roughly 8 production methods; forging 19 style sheets would mean 19 canary sets and 19 chances to drift.

Stage 4.5 — Calibrate

Prove the machine and learn the models before anything is locked. A half stage on purpose: it sits between configuration and the forge, it runs once per client, and nothing it produces is canon.

The charter says that at volume the spec flows from what can actually be produced. Calibration is where that is learned. Skipping it means the first thing anyone discovers about a model's real behaviour is discovered inside a committed style version, which is the expensive place to find it.

What it is for

Rules, so a sandbox stays a sandbox

  1. No style version is created. Calibration precedes the forge; it does not quietly become one.
  2. No canary, no delivery, no exemplar promotion. Output is disposable by construction, and cannot be laundered into the library later.
  3. Cheap by default — draft tier, small counts, its own ceiling. The point is information per dollar, not quality.
  4. Findings are the deliverable. They land in docs/corpus/models/<key>.md per 12-TOOL-CORPUS-SOP.md, and in the notes the lane's style sheet will inherit. Images are a by-product; what was learned is the output.
  5. It is allowed to fail. A calibration run that produces nothing usable but explains why is a success. This is the one stage where being wrong is the expected outcome rather than a defect.

Exit: one brief has run end-to-end and landed a candidate with full provenance and a ledger entry, and the calibration log can say what each routed model does for this client's registers.

Output: clients/<id>/calibration.md — template in templates/.

Stage 5 — Per lane, every time

#StepOutput
1Capability, not model — vector, diagram, photoreal, stylized-volume, typographic, chart, edit, motioncapability:
2art style forge — intake, probe, converge, validatestyles/<style>@1/
3Fix the canary subjects — six, and never edited aftercanary_subjects
4Spec — dimensions, format, transparency, DPI, safe areasspecs: block
5Naming and deliver_to — from the client's schema if it has onenaming:, deliver_to:
6QA contract — including the failure mode found in Stage 1in style.yaml
7Autonomy — starts at 0. Always.autonomy: 0

The gate: a lane is not onboarded until its canary set renders and a human has looked at it. A style sheet that has never produced an image is a guess.

Templates live in templates/:

TemplateFilledStage
templates/client-intake.mdonce per client1–2
templates/visual-mission.mdonce per client, revised deliberately3
templates/calibration.mdonce per client4.5
templates/lane-request.mdevery requestongoing

A client that already maintains a machine-readable manifest should not fill out lane-request.md — consume their artifact instead. Two sources of truth for the same list is how a 204-piece catalog silently becomes two 204-piece catalogs that disagree.

4. The work loop

05-PIPELINE.md is the mechanical description. This is the human one.

request → triage → brief → estimate → run → QA → review → asset → delivery
  1. Request arrives through the bridge, or is typed directly.
  2. Triage — is there a lane for this? If not, stop: onboarding a lane (§3) is the work, not squeezing the ask into a lane that nearly fits.
  3. Brief — art brief or the art-brief skill. A vague ask gets a clarifying question, never an improvised prompt.
  4. Estimate — printed before any spend, always. Over WOOT_RUN_CEILING, it stops and asks. This is non-negotiable #2 and has no exceptions.
  5. Draft pass first for anything exploratory. Finals are for converged work.
  6. Run → candidates.
  7. QA — automated, mechanical then perceptual. Nothing reaches a human that fails its own spec.
  8. Review — contact sheet, keystroke verdicts, critique captured. 06-REVIEW-UX.md. The critique is the part that compounds; a bare reject teaches nothing.
  9. Asset — approved candidates only, normalized, named, provenance intact.
  10. Delivery — into the client repo at deliver_to. Never without a verdict unless the lane is at autonomy 3 and a human put it there.

Review structure — who approves what

GateWhoQuestion
Mechanical QAcodeDoes it meet the spec?
Perceptual QAClaude visionDoes it meet the style's QA contract?
VerdictJustinIs it right? — the only gate that authorizes delivery
Corpus write-backproposed by Claude, confirmed by JustinIs this lesson durable?

There is no separate client-side approval step. The verdict is the approval; delivery is what happens after it. Adding a second sign-off inside the client project would mean images sitting in two review queues with no one owning either.


5. The bridge — how clients ask, and collect

The bridge has two halves and they have different problems. The local half is declared and running; the hosted half is designed and not built.

5a. Local clients — lib/bridge.mjs, art intake <client>

When a client is a checkout on this machine there is no transport problem: the filesystem is the transport. What was missing was a contract — until 2026-08-19, docade's request source was a comment in client.yaml and a default parameter in lib/manifest.mjs. Both correct for docade, neither a declaration, so client #2 had nowhere to say it differed.

Each client declares, in client.yaml, every path WootBuild takes as input and what role it plays, because "the brief changed" and "the work-list changed" are not the same event:

RoleWhat it isA change means
worklistthe machine-readable piece list — the work itself. Exactly onepieces added, removed or respecced; lanes may not cover them
briefprose the client wrote about intenta person reads it; nothing derives automatically
styletheir brand guide or identity packagea style sheet, or a copy we hold, may be out of date

art intake <client> hashes each declared read, reports what moved since last time, re-reads the work-list without overwriting the snapshot — the diff is the product — and assesses whether new pieces land in an existing lane or need a new one. It is free, offline, and writes nothing without --accept.

It proposes and never acts. It creates no lane, and it never writes into a client repo. A new lane is forged by a person, and its first phase is field — the bake-off — which is a gate. That is deliberate: Justin, 2026-08-19, asked for notification and assessment, "if it's a new lane, enter into the process once the user is present" — not an automated loop.

Why it was built during the easy client. Four clients are waiting and docade's needs are straightforward. A stale snapshot against a simple manifest is a cheap lesson; the same bug against a complex client, while that client waits, is not. It paid for itself on the first run: Number('vector') is NaN, JSON.stringify writes NaN as null, and fourteen of docade's declared sizes had been silently discarded — og-image at 1200x630 among them, which would have been produced square. Same class as advisory 04's room-treatments-at-half-size, and found the same way: by diffing rather than overwriting.

Delivery is declared, not yet built. bridge.delivers.receipt names where an index of delivered work goes in the client repo, so the path is agreed before the first delivery rather than invented at delivery time. When art deliver writes it, it goes under the same human verdict gate as the assets — never unattended.

5b. Hosted clients — Supabase

Decision, 2026-08-17: Supabase, reversing the Phase 0 refusal.

01-ARCHITECTURE.md declined Supabase, and recorded the condition for revisiting it: evidence, specifically a hosted review UI or a second machine. The evidence arrived in a different form than expected, and is stronger:

The client Claude projects are hosted on claude.ai and cannot read this disk. The original refusal assumed both sides were local — that docade's manifest was a file WootBuild could simply read, which is true only for the Claude Code side of docade. A hosted Claude project with a Supabase connector can write a request row; it cannot write a file into ~/Projects/wootbuild. Files-are-truth was never the thing in question; reachability was, and it was assessed wrong.

What this does not change: non-negotiable #1 still holds without amendment.

Supabase is a transport, not a source of truth. A request lands in Postgres, and WootBuild immediately materializes it as a file under requests/. The file is truth. Everything in Supabase must be reconstructible from the repo, and art reindex must never need it.

Sequenced the same way — an inbox and an outbox, not a shared database:

DirectionCarriesTruth lives
Client → WootBuildrequests: lane, free text, count, deadlinematerialized to requests/ on arrival
WootBuild → Clientdelivered assets: URL, filename, spec, verdict, provenancethe library + the client repo

Isolation is the whole risk here, and it is the exact collision the charter warns about. The global SUPABASE_* on this machine points at salon-structure production. WootBuild gets its own Supabase project and its own WOOT_SUPABASE_* credentials. guard-env.sh already denies any command copying a global into WOOT_ scope, and that guard stays exactly as it is.

Known blocker, not yet fixed: assertIsolation() in lib/config.mjs flags a leak when a global SUPABASE_URL exists and WOOT_SUPABASE_URL is set — comparing presence rather than value. A dedicated project trips it as a false positive. The fix is to compare values, which preserves the real protection (catching an actual copy) and must land before the bridge is wired.

Not building yet: the browsable gallery and approval UI. Deferred until docade has assets, because a gallery designed against an empty library is a guess. When it is built, it reads from R2 — already provisioned and verified writable — and Cloudflare Pages, not Vercel. One credential relationship instead of two.


6. Session protocol

Start

  1. The SessionStart hook injects handoff, git state, and the last session's heading before the first prompt. No action needed.
  2. art doctor — always, in any session that will generate.
  3. art models --verify before the first spend of a session that generates. Free, read-only, and it has caught six phantom model ids in one pass.

During

Close — the standing expectation, not something to be asked for.


7. What this document does not decide

Recorded so they are not mistaken for oversights: