---
title: "How to build with Claude Code workflows"
description: "Moving from markdown plans to deterministic, executable workflow files with Claude Code Workflows. Two real workflow plans, agent caps, and the cost of letting agents run free."
pubDate: "2026-08-27"
heroImage: "/workflow-screenshot.webp"
---

## Context

At work I developed an internal tool to share and build on top of ideas of others. You can think internal Vercel, only that deploys in seconds in a few clicks, and you can share a secure URL with your peers.

This tool has templates, so designers can quickly create a feature in Claude Code, iterate on it, share. This contains all our design system components, tokens, and so on. Then this interactive super hi-def prototype is done, they can share the work with a developer who'll adapt to production code. The question is: how can we make this process simple, and make it a breeze to go from prototype to production code?

### Ideas build on top of each other

Then I saw one prototype created by another designer, and he included a `/doc` section to pass to the developer what he built, why, patterns, and some important details, that would require back and forth, Figma duplication or would be just missed from implementation. There' even an agent-ready `handover.md` in the package.

This `/docs` is also design documentation. Other designers jumping in the project would understand what was done and why, reducing silos and reinventing the wheel.

So, I've rolled up my sleeves and went into improve and implement this for all designers, and I'll do it using Claude Code Workflows.

## How to build using CC workflows

As I talked about recently in the [Boiling the Ocean](https://boiling.mauriciowolff.com) talk, I've been using Claude Code workflows, before I was just using agent teams. The difference is that if you just create an `.md` plan with Agent Teams, the model will do a lot of guess work, and you might either spend a long time editing the plan, or will have to fix later.

With workflows, you get a deterministic plan (a `.js` file) written by the planner agent.

That's what I was missing: plan in code, that you can just open in [Zed](https://zed.dev) and change, knowing the steps your agent team will do.

## A key item: how many agents?
Real story: a friend created a workflow with [Fable](https://www.anthropic.com/claude/fable) without setting max agents. Result: 1h later, the weekly usage was gone ($100 plan) after it spawned 99 agents to do the work :mindblown:

So, after hearing that, I always set a max number of agents, which is a hunch based on the complexity of the task. If you're "generous", I'd say 12 is a nice number. For simple parallel things I usually set 5.

You can also ask the planner to set model/effort for the task. Fable has the tendency to offer Haiku for reading stuff, which I politely decline and choose Sonnet/low instead.

At the end, you'll get a JS file showing the steps, when, and how they run.

> **And you can just open it and edit. It's real code.**

Below you'll find 2 plans for this project, the first one I didn't set models and effort (was multitasking and wanted to get it done).  The second one I did, and this is what I usually do/edit in the workflow.

In [Claude Code](https://code.claude.com/docs/en/workflows) you can use `/workflows` to monitor progress and jump in if necessary.

The planner gave me a 3 parts plan. **Recon** maps the repo and references. **Builders** write the skill, templates, scripts, and wire everything together, and **Verifiers** review, dry-run, edit copy. I have a rule that agents can edit, but not commit stuff.
## Why this is cool

For me, this is the golden child of **inference** (models guessing stuff) and **determinism** (code).

Now your agents can follow your plan to the letter (it's code) and not guess the next step or second-guess the instructions based on a markdown file.


![Claude Code workflow view: recon, build, and verify agents in progress](/workflow-screenshot.webp)

```js
export const meta = {
  name: 'handover-build',
  description: 'Build the generic /handover skill: templates, extraction scripts, route + CI wiring',
  phases: [
    { title: 'Recon', detail: '3 read-only mappers' },
    { title: 'Build', detail: '5 writers, disjoint paths' },
    { title: 'Verify', detail: 'review, mechanical dry-run, copy sweep' },
  ],
}

// 11 agents total: 3 recon + 5 build + 3 verify.
// GENERIC ONLY: no feature is built or used as an example; templates carry
// placeholders, never real content. A related redesign is a separate later
// branch. Reference docs are anatomy examples only, not content.
// Agents EDIT ONLY, never commit. Main thread reviews, runs check, commits.

const REPO = '~/dev/pilot'
const DESIGN = `${REPO}/.plans/2026-08-21-handover-docs-design.md`
const DOCS_REFERENCE = '~/Downloads/handover-docs-breakdown.md'
const TOUCHPOINTS_URL = 'https://pilot.preview.internal/docs/communication-touchpoints'

const RECON_SCHEMA = {
  type: 'object',
  required: ['map', 'constraints'],
  properties: {
    map: { type: 'array', items: { type: 'object', required: ['what', 'where'], properties: {
      what: { type: 'string' }, where: { type: 'string' }, note: { type: 'string' } } } },
    constraints: { type: 'array', items: { type: 'string' } },
  },
}

phase('Recon')

const reconRepo = agent(`Read-only recon in ${REPO} (use absolute paths). Map, with file:line:
1. server.ts route table shape — how a new static route with custom headers would be added; where /handover/* should slot.
2. .github/workflows/no-sketch-merge.yml — the exact mechanism, so a second check (block handover/ in PRs to main) can be added in the same style.
3. scripts/gen-cheatsheet.ts — how it reads the theme export and how staleness is checked, as the pattern for a token head-block generator.
4. scripts/guardrails.ts — how .status is read and how the script is structured (imports, error reporting), as the house style for new scripts.
Return the map plus a constraints list (AGENTS.md rules that bind this work: @/ imports, script conventions, what bun run check enforces).`,
  { label: 'recon-repo', phase: 'Recon', schema: RECON_SCHEMA, agentType: 'Explore' })

const reconSpec = agent(`Read ${DESIGN} (design v2) and ${DOCS_REFERENCE} (a breakdown of how the /docs pages were actually built). Produce a single build checklist for a /handover skill and its two templates (HTML spec + handover.md). Every rule the templates and skill must encode, as short imperative items with the source section cited: the five-part chapter sequence, the two spec-table schemas (matrix + headerless key/value), the provenance head block with token→hex→alias table and "do not hand-tune", SHA stamp, runs accumulate under handover/<sha7>/, screenshots-not-playgrounds media policy (values never only in images, alt text), the preflight gates (strict check, pseudo-loc, tab-through, naming settled, constants named in code, behavior-only scope), the three high-drift sentence types, the mechanical lints (stale build language flag-not-delete, orphan hex vs :root, dead CSS, section-number/comment desync), the deletion test, the human-gate list (deletions, post-mortem→rule, taxonomy, copy synthesis, recommendations, visual sign-off), handover.md sections (constants+tokens, rules, whys, fidelity notes, final strings, open questions, DS components, SHA + preview + walkthrough link), the ask-once Loom/Talktrack step, and the facts-not-instructions tone rule. Return as markdown.`,
  { label: 'recon-spec', phase: 'Recon', agentType: 'Explore' })

const reconTouchpoints = agent(`Fetch ${TOUCHPOINTS_URL} (curl is fine). It is a communication-touchpoints doc: a catalog of every user-facing message a feature adds, written for content design and GTM. Extract its reusable anatomy into a template schema: per-touchpoint fields (Surface, Component, What it does, Why it exists, trigger rationale, the job, rendered copy mock), the numbering scheme (1..N plus F1..FN free-tier variants), how variants and states are grouped, how copy is rendered vs described, and any recurring copy-rule patterns. Output: a field-by-field schema plus 2 short filled examples lifted from the page. This becomes a third page type in the project's /handover docs.`,
  { label: 'recon-touchpoints', phase: 'Recon', agentType: 'Explore' })

phase('Build')

// Chain S — skill + spec template (need reconSpec)
const skillChain = reconSpec.then(spec => parallel([
  () => agent(`In ${REPO}, create .claude/skills/handover/SKILL.md — the /handover skill. Edit only, do NOT commit. Frontmatter: name handover; description triggers on "handover", "/handover", "generate the handover", "spec for devs". Body = the pipeline from this checklist (verbatim rules, do not soften):\n\n${spec}\n\nStructure: 1 Preflight (refusal conditions with the exact commands: bun run check:strict, pseudo-loc ?pseudo=2, tab-through §11; naming/constants/scope gates), 2 Scope (diff vs main → chapter plan, confirm with user; ask ONCE for a Loom/Talktrack link), 3 Extract (run scripts/gen-handover-tokens.ts for the head block; constants/keyboard/copy from code with file:line), 4 Capture (agent-browser single-state screenshots into handover/<sha7>/assets/), 5 Write (templates in .claude/skills/handover/templates/ — reference them, do not inline), 6 Lint (run scripts/handover-lint.ts; flagged items are questions for the user, not auto-fixes), 7 Human gates (list them; the skill nominates, never decides), 8 Commit to branch + print preview URL. Also update AGENTS.md's Modes "Pilot scope" line to include the /handover skill as live. Match AGENTS.md's writing style.`,
    { label: 'skill-author', phase: 'Build' }),
  () => agent(`In ${REPO}, create .claude/skills/handover/templates/spec.html and run-index.html. Edit only, do NOT commit. spec.html: self-contained single-file HTML template for the interaction spec (inline CSS, no webfonts, no build step) implementing this checklist's page rules:\n\n${spec}\n\nInclude: provenance head block as a CSS comment with {{SLOTS}} for SHA/date/branch/preview/walkthrough + the token→hex→alias table slot (generated, "do not hand-tune"), the five-part chapter skeleton with one worked placeholder chapter, both spec-table schemas, a screenshot figure pattern (figcaption + meaningful alt slot), and a chapter nav if >1 chapter. run-index.html: run history list, newest first, row = sha · date · intent · walkthrough link. Placeholder syntax: {{UPPER_SNAKE}} slots + <!-- repeat:chapter --> blocks. Design language: match the project's served docs prototype (fetch to reference), values from real theme tokens noted by name.`,
    { label: 'template-spec', phase: 'Build' }),
]))

// Chain C — companion templates (need reconSpec + reconTouchpoints)
const companionChain = Promise.all([reconSpec, reconTouchpoints]).then(([spec, tp]) => agent(
  `In ${REPO}, create two templates under .claude/skills/handover/templates/. Edit only, do NOT commit.\n(1) handover-md.md — the agent-ready companion file template (~200 lines when filled): sections per this checklist (constants+tokens table, per-behavior rules lists, whys, fidelity notes, final strings block, open questions, DS components used, gaps rows, SHA + preview URL + walkthrough). Tone rule at the top as an HTML comment: facts about the prototype, never instructions to the dev.\n\n${spec}\n\n(2) touchpoints.html — the communication-touchpoints page type (content design + GTM audience), same self-contained single-file conventions as spec.html, implementing this schema:\n\n${JSON.stringify(tp)}\n\nUse {{UPPER_SNAKE}} slots and <!-- repeat:touchpoint --> blocks.`,
  { label: 'template-companions', phase: 'Build' }))

// Chain W — scripts + wiring (need reconRepo; scripts also gets the lint list from reconSpec)
const scriptsChain = Promise.all([reconRepo, reconSpec]).then(([repo, spec]) => agent(
  `In ${REPO}, create scripts/gen-handover-tokens.ts and scripts/handover-lint.ts. Edit only, do NOT commit. Repo map:\n${JSON.stringify(repo)}\n\ngen-handover-tokens.ts: read the theme export exactly the way scripts/gen-cheatsheet.ts does; emit the provenance token block (raw name, hex, $semantic-alias per line) plus a :root CSS custom-property block, to stdout or --write into a target file's marked region.\n\nhandover-lint.ts: takes a handover run dir; checks from this list (report, exit 1 on findings, flag-not-fix): stale build language (not yet, not built, "(later)", coming soon, build status, by design), hex literals in the page body missing from the :root block, CSS classes with zero matching elements, <span class="idx">NN</span> vs preceding <!-- NN --> comment mismatches, top-level let/const names colliding with window globals in inline scripts (chrome, screen, name, length, status, origin, history, content).\n\n${spec}\n\nStyle: match guardrails.ts (bun, node:fs, no deps).`,
  { label: 'scripts', phase: 'Build' }))

const wiringChain = reconRepo.then(repo => agent(
  `In ${REPO}, two edits. Edit only, do NOT commit. Repo map:\n${JSON.stringify(repo)}\n\n(1) server.ts: add a static /handover/* route serving the handover/ dir with Cache-Control: no-store, following the existing route-table style; comment states why no-store (a cached copy is indistinguishable from a broken page while a run is being iterated). 404 cleanly when the dir is absent.\n(2) .github/workflows/no-sketch-merge.yml: extend in the same style to also fail a PR to main that contains a handover/ path (handover docs live on branches only). Keep the two checks' messages distinct.`,
  { label: 'wiring', phase: 'Build' }))

const builds = await Promise.all([skillChain, companionChain, scriptsChain, wiringChain])
log('Build done: ' + builds.filter(Boolean).length + '/4 chains returned')

phase('Verify')

const verify = await parallel([
  () => agent(`Review the uncommitted changes in ${REPO} (git status + git diff). One line per finding, severity-tagged, format path:line: severity: problem. fix. Check against AGENTS.md hard rules: banned deps/imports, relative imports, hardcoded values, script conventions vs guardrails.ts house style; plus generic-ness: templates must contain ONLY placeholders and worked placeholder examples, no real feature content. No praise, no scope creep.`,
    { label: 'review', phase: 'Verify', agentType: 'code-reviewer' }),
  () => agent(`Mechanical dry-run in ${REPO}. Do not commit anything. 1) Run bun scripts/gen-handover-tokens.ts — output parses, every hex matches the installed theme. 2) Create a throwaway fixture dir /tmp/handover-fixture with a page seeding one violation of EACH handover-lint rule; run bun scripts/handover-lint.ts against it — every seed caught, exit 1; then a clean fixture — exit 0. 3) Start the dev server if not running (PORT free per AGENTS.md §7), create a minimal handover/index.html, curl /handover/ — expect 200 + Cache-Control: no-store; then remove the test file. 4) Simulate the CI gate: confirm the no-sketch-merge.yml change would fail on a fixture file list containing handover/x and pass without it (test the yml's detection command directly). Report pass/fail per item with the exact failing output.`,
    { label: 'dry-run', phase: 'Verify' }),
  () => agent(`Copy sweep in ${REPO}: every NEW user-visible string in the uncommitted diff (template placeholder copy, section labels, lint/skill messages shown to users). Check against AGENTS.md §13: sentence case, U.S. English, no please/sorry, verb-first CTAs, "More options" not "More actions", … not ..., proper nouns keep caps. Fix violations directly in the files (edit only, no commits); list what you changed.`,
    { label: 'copy-sweep', phase: 'Verify' }),
])

return {
  builds: builds.map(b => (typeof b === 'string' ? b.slice(0, 400) : b)),
  review: verify[0],
  dryRun: verify[1],
  copySweep: verify[2],
}
```

## Round 2: models and efforts per task

Same day, iteration: a new dev toolbar (3px colored status bar that expands on hover). This time I set models and efforts. I mean, the planner agent did, I reviewed. Recon and sweeps run on Sonnet, UI on Fable, and review as a dedicated agent (tip: sometimes I do [`codex -p`](https://github.com/openai/codex) with Sol High for adversarial reviews, overkill here).

9 agents, same steps names. I could improve the naming, editing the `.js` file. But that's fine.

```js
export const meta = {
  name: 'devbar-build',
  description: 'Build the new dev bar (3px top-center bar + peek/pin panel) on feature/dev-status-bar, full replace of the old dev toolbar',
  phases: [
    { title: 'Recon', detail: '2 read-only mappers (sonnet/low)' },
    { title: 'Build', detail: 'data bake (sonnet), UI core (high effort), docs (sonnet/low)' },
    { title: 'Verify', detail: 'review, a11y pass, runtime + screenshots, copy sweep' },
  ],
}

// 9 agents (cap 12): 2 recon + 3 build + 4 verify. Agents EDIT ONLY, never
// commit (exception: none). Main thread reviews, runs check, commits.
// Everything lands on feature/dev-status-bar per the project owner's call.

const REPO = '~/dev/pilot'

// The settled spec — single source for every build prompt.
const SPEC = `THE DEV BAR (settled spec, do not redesign):
- Rest: 3px colored bar, top center, full app (mounted once at app root, present on every route). Color = mode: sketch yellow #BA8A12-family, feature blue, GREEN only when newest handover/<sha7>/ SHA == built commit (stale docs = blue + stale badge in panel), neutral grey on main (no mode semantics there). Color-only rest state is acceptable (dev chrome); everything else lives one hover away.
- Reveal: hover near the bar PEEKS a ~56px translucent panel with backdrop blur; CLICK PINS it (so kebab/toggles are usable); Esc or click-away closes. The bar is a REAL focusable button with a taller invisible hit area (~16px strip, pointer-events only on bar + halo so it never swallows clicks meant for content), aria-expanded, plus a keyboard shortcut to open the panel (suppressed while typing — copy the main toolbar's suppression pattern).
- Panel contents: LEFT mode letter + word (S · Sketch / F · Feature; main shows the version tag instead — the old toolbar version tooltip's job). MIDDLE owner · age · drift (2↓, red #D8182C text, the ↓ glyph carries meaning) · docs slot (absent when no runs; 'docs · <sha7> · stale' when behind; live link 'docs · <sha7>' → /handover when current) · debt chip '⚠ N token slips · gates at /handover' when N>0, with a Tooltip carrying breakdown + 'bun run check:strict lists them'. Sketch panels also show the .status intent line. RIGHT: existing tools as icons (design inspector toggle, pseudo-loc, annotations) + kebab (DropdownMenu).
- Kebab: feature flags from src/stores/experimental.ts registry (per-viewer, reactive via useStore) + built-ins: hide dev chrome (?nochrome twin), pseudo-loc variants (2 / cjk / ar). Empty flags state: 'No flags. Register one in experimental.ts'. A small note that toggles are per-viewer.
- Drift click: dialog (DS Dialog via existing DialogShell pattern) 'Your branch is N commits behind main' + COPY UPDATE PROMPT button copying: "Rebase this branch onto origin/main (rebase, not merge — earlier branch commits may have been squash-merged separately and rebase auto-drops the duplicates). Resolve conflicts keeping this branch's intent; run bun run check; push. The preview redeploys on push." Sketch dialog adds 'updating is your call'; feature adds 'a feature PR must be up to date with main before merge'. Honest risk line: 'If main and this branch touched the same files, your agent will ask you to arbitrate.' NEVER any server-side rebase.
- ?nochrome URL param + kebab toggle hide ALL dev chrome for the session.
- Panel footer: 'built <sha7> · <relative time>' — all data is deploy-time, say so.
- z-index 9999 for bar, panel, and portals (selection overlay sits at 310; menus have been eaten before).
- Alerts model: bar = ambient color only; NEVER Toast/Banner/modal from dev chrome; panel chips = counts as of build; tooltips = next detail layer.
- Data: baked at build like BUN_PUBLIC_APP_VERSION — mode (from .status presence/content), owner, age, drift, docs sha + currency, debt count, built sha + timestamp.
- FULL REPLACE: the old dev toolbar components are deleted in this change after their features migrate into the panel. Nothing may silently disappear — work from the recon inventory.
- Rules: real DS components first (DropdownMenu for kebab, Tooltip, Dialog; the bar/panel shells are dev chrome — tokens for every value except the sanctioned mode-color constants block, which gets a docs/design-system-gaps.md row), @/ imports, styled() over inline css >4 props, a11y (focus visible, aria-expanded, dialog focus trap + return-to-trigger, nothing focusable while hidden — use inert or visibility on the closed panel).`

const RECON_SCHEMA = {
  type: 'object',
  required: ['map', 'inventory', 'constraints'],
  properties: {
    map: { type: 'array', items: { type: 'object', required: ['what', 'where'], properties: {
      what: { type: 'string' }, where: { type: 'string' }, note: { type: 'string' } } } },
    inventory: { type: 'array', items: { type: 'string' } },
    constraints: { type: 'array', items: { type: 'string' } },
  },
}

phase('Recon')

const reconToolbar = agent(`Read-only recon in ${REPO}. Map with file:line:
1. EVERYTHING under src/components/dev/ — every component, what it does, how it mounts (find the mount point in App.tsx or wherever), which stores it reads.
2. The FULL feature inventory of the current dev toolbar as user-visible capabilities (inspector toggle incl. the profile-menu switch path, pseudo-loc trigger + ?pseudo param handling in src/lib/pseudo-loc.ts, annotations toggle + shortcut, version display incl. where BUN_PUBLIC_APP_VERSION renders, anything else) — this list is the contract for a full replace; nothing on it may be lost.
3. Conventions the new bar must follow: z-index values in use (selection overlay 310, menu pins 500), portal patterns, common/a11y.tsx primitives (useFocusTrap, VisuallyHidden), the main toolbar's typing-suppression pattern for shortcuts, DialogShell, lib/motion.ts primitives, how ?pseudo params are parsed.
Return map + inventory (the capability list) + constraints.`,
  { label: 'recon-toolbar', phase: 'Recon', schema: RECON_SCHEMA, agentType: 'Explore', model: 'sonnet', effort: 'medium' })

const reconData = agent(`Read-only recon in ${REPO}. Map with file:line how build-time data gets baked and how each dev-bar datum can be computed:
1. package.json dev/build scripts: exact shape of the BUN_PUBLIC_APP_VERSION git substitution (the pattern to extend).
2. How scripts/guardrails.ts collects polish findings (the 'polish' array) — plan the smallest change to add a --count flag printing ONLY the number (exit 0 always) so the build can bake a debt count.
3. Git one-liners for: branch owner (last committer or gh user? propose the most stable), branch age (first commit on branch vs today), drift (commits behind origin/main), current sha7. Note traps: shallow clones on build runners, detached HEAD at build.
4. How to detect the newest handover/<sha7>/ run dir and compare to HEAD (design v2: .plans/2026-08-21-handover-docs-design.md).
5. How .status is read today (guardrails.ts SKETCH_MODE regex) — mode + intent extraction at build time.
Return map + inventory (the list of BUN_PUBLIC_* vars to bake, with the command for each) + constraints.`,
  { label: 'recon-data', phase: 'Recon', schema: RECON_SCHEMA, agentType: 'Explore', model: 'sonnet', effort: 'medium' })

phase('Build')

// Data bake: package.json + guardrails --count. No src/ UI files — disjoint from the UI agent.
const dataChain = reconData.then(rd => agent(
  `In ${REPO}, implement the build-time data bake. EDIT ONLY, do not commit. Recon:\n${JSON.stringify(rd)}\n\n1. scripts/guardrails.ts: add a --count mode that prints only the polish-finding count (number, newline) and always exits 0; normal and --strict behavior unchanged.\n2. Bake these as BUN_PUBLIC_* in BOTH the dev and build scripts in package.json, following the existing APP_VERSION substitution style (keep each substitution resilient: fall back to empty string, never fail the build): MODE (sketch|feature from .status presence), INTENT (sketch only), BRANCH_OWNER, BRANCH_AGE_DAYS, DRIFT (commits behind origin/main, empty when unknown), BUILT_SHA (sha7), BUILT_AT (unix seconds), DEBT_COUNT (bun scripts/guardrails.ts --count), DOCS_SHA (newest handover/<sha7> dir name or empty).\n3. If the one-liners get unwieldy inline, extract to scripts/branch-meta.ts invoked per-var (bun scripts/branch-meta.ts owner etc.) — pick the cleaner option and keep dev-server startup fast (guardrails --count on every dev start is fine only if <1s; otherwise bake it in build only and let dev read 0 — decide, note the decision in the script header).\nRun bun run check before finishing. ${SPEC}\n`,
  { label: 'build-data', phase: 'Build', model: 'sonnet', effort: 'medium' }))

// UI core: everything in src/components/dev/ + App.tsx mount + old toolbar removal + SKILL.md capture line.
const uiChain = Promise.all([reconToolbar, reconData]).then(([rt, rd]) => agent(
  `In ${REPO}, build the new dev bar and fully replace the old dev toolbar. EDIT ONLY, do not commit. src/components/dev/ is owned by our team, you're clear to edit.\n\nToolbar recon (your migration contract — every inventory item must exist in the new panel or be consciously listed as dropped in your final report):\n${JSON.stringify(rt)}\n\nData recon (read the baked vars from these names; the data agent implements the baking in parallel — code against the BUN_PUBLIC_* names in the spec, with safe fallbacks for empty values):\n${JSON.stringify(rd)}\n\n${SPEC}\n\nStructure suggestion (yours to adjust): src/components/dev/DevBar.tsx (bar + panel), DevBarMenu.tsx (kebab), DevBarDriftDialog.tsx, a small devChrome store or util for ?nochrome + pin state. Delete the superseded old toolbar files and their mount; mount DevBar once at app root. Also update .claude/skills/handover/SKILL.md's capture step: screenshots are taken with ?nochrome so dev chrome never lands in spec figures.\nAdd the sanctioned mode-color constants block (single commented block) and a corresponding row in docs/design-system-gaps.md.\nRun bun run check before finishing; fix what it flags. Return: files created/deleted, the migration checklist with each inventory item's new home, and any deliberate drops.`,
  { label: 'build-ui', phase: 'Build', effort: 'high' }))

// Docs: AGENTS.md pilot-scope line — after UI so the wording matches what exists.
const docsChain = uiChain.then(ui => agent(
  `In ${REPO}, one edit. EDIT ONLY, do not commit. The new dev bar just replaced the old dev toolbar on this branch (summary: ${String(ui).slice(0, 1200)}). Update AGENTS.md's Modes 'Pilot scope' paragraph: the status tag no longer 'lands separately' — the dev bar (3px top-center bar + panel: mode, owner/age/drift, docs link, debt chip, tools, flags kebab, ?nochrome) is part of the pilot. Match the file's existing style and keep it to the existing sentence's footprint.`,
  { label: 'docs-sync', phase: 'Build', model: 'sonnet', effort: 'low' }))

const builds = await Promise.all([dataChain, uiChain, docsChain])
log('Build done: ' + builds.filter(Boolean).length + '/3 chains returned')

phase('Verify')

const verify = await parallel([
  () => agent(`Review the uncommitted changes in ${REPO} (git status + git diff, includes new untracked files). One line per finding, severity-tagged, path:line: severity: problem. fix. Check: AGENTS.md hard rules (DS-first — kebab must be DropdownMenu, dialog must be the DS dialog; tokens everywhere except the sanctioned mode-color block with its gaps row; @/ imports; styled extraction), a11y (bar is real button, aria-expanded, closed panel not focusable — inert/visibility, dialog focus trap + return, shortcut suppressed while typing, drift not color-only), z-index 9999 discipline, and the migration contract: every old-toolbar capability has a new home (read the build-ui agent notes in the diff). No praise, no scope creep.`,
    { label: 'review', phase: 'Verify', agentType: 'code-reviewer', effort: 'high' }),
  () => agent(`Keyboard/a11y pass on the new dev bar in ${REPO}, code-level (no browser): trace the tab order and key handling in src/components/dev/DevBar*.tsx. Verify concretely, citing lines: (1) the bar button is reachable and 3px-visual/16px-hit; (2) closed panel contributes nothing focusable (inert or visibility); (3) pinned panel: Esc closes and focus returns to the bar; (4) kebab is a real DropdownMenu (gets ARIA free); (5) drift dialog traps focus and returns it; (6) the open shortcut is suppressed while typing; (7) drift/debt states carry non-color cues. Report violations with fixes; apply small fixes yourself (EDIT ONLY, no commits), flag structural ones.`,
    { label: 'a11y-pass', phase: 'Verify', effort: 'medium' }),
  () => agent(`Runtime verify + screenshots in ${REPO}. Do not commit code changes; committing NOTHING at all — write screenshots to .github/pr-assets/ as plain files. 1) bun run check && bun run check:strict must pass. 2) Free port check (lsof -nP -iTCP:4444 -sTCP:LISTEN; use PORT=<free> if busy), bun dev, open localhost (NEVER [::1]). 3) Verify in the served page: bar renders at top center; hover peeks the panel; click pins; ?nochrome removes all dev chrome; baked BUN_PUBLIC_* values present (view source or window check). 4) Capture with a browser automation tool (targeted, sanctioned): rest bar (feature blue), peeked panel, pinned panel with kebab open, drift dialog (if drift is 0, relaunch dev with BUN_PUBLIC_DRIFT=2 env override to force the state). Save to .github/pr-assets/devbar-*.png. 5) Sketch color variant: relaunch dev with BUN_PUBLIC_MODE=sketch, capture rest bar. 6) Afterwards: stop the dev server you started and close the browser session, verifying no helper processes remain. Report pass/fail per item with exact output.`,
    { label: 'runtime-shots', phase: 'Verify', model: 'sonnet', effort: 'medium' }),
  () => agent(`Copy sweep in ${REPO}: every NEW user-visible string in the uncommitted diff (panel labels, tooltips, kebab items, drift dialog copy, empty states, the update prompt text). AGENTS.md style rules: sentence case, U.S. English, no please/sorry, verb-first CTAs, … not ..., proper nouns keep caps; no fixed widths on text slots, truncation keeps a recovery path, minWidth:0 on truncating flex children. Fix directly (EDIT ONLY, no commits); list changes.`,
    { label: 'copy-sweep', phase: 'Verify', model: 'sonnet', effort: 'low' }),
])

return {
  builds: builds.map(b => (typeof b === 'string' ? b.slice(0, 600) : b)),
  review: verify[0],
  a11y: verify[1],
  runtime: verify[2],
  copy: verify[3],
}
```
## So... is this worth the effort?

I'd say so. building like this gives you more control of what agents will do, costs, and it is, at least for now, the best way to mix the model's way to infer things and deterministic code, where you know how it will work. Or at least have a very good idea on how it's going to.

So, next time you'll have to do something that requires some level of complexity, spend [your sweet time planning](https://boiling.mauriciowolff.com/#/22) then [Turn the plan into a workflow](https://boiling.mauriciowolff.com/#/30) and let them do the work. 

PS: I've found [DeepSeek v4 Flash](/blog/swipetris) an awesome agent to delegate the workflows as well, with `pi -p`.