Loom for developers: agents that build with your real components

Your agent should import the real Button, not write a new one. Loom gives AI coding agents the exact components, properties and tokens of your system through an MCP server, checks what they write against your rules, and turns every change into a pull request you review.

The rules live in people's heads, and agents cannot read heads

Agents guess. Your assistant writes a settings page before lunch. It hard-codes #2563eb, adds padding: 13px, builds a local modal and uses a property that was renamed last year. Every pull request needs the same review comments.

The rules live in people's heads. Which token for a danger border? Is a className override allowed on Card? Is variant="danger" deprecated? The answers exist, but not in a form a linter or an agent can read.

Drift is found too late. Raw values and local rebuilds pile up across products. Nobody can say how many, or which ones matter.

Changes to the system break consumers. A renamed property or a changed default ships as a minor version, and someone finds out in production.

Two platforms, two behaviours. The web and native versions of a component drift apart, because nothing checks them against the same rules.

Loom gives agents the real system and checks what they write

Your system, over MCP

Any agent that speaks MCP gets 17 tools: search the system with a confidence level, read a contract with the exact import, read tokens and rules, get the whole system as one DESIGN.md file, check a composition, audit code, propose a change, classify a need as reuse, extend or new, and list unmet needs. On our reference system, calls take between 2 and 72 milliseconds. Working

Search reports "no strong match" instead of a weak guess, so the agent proposes what is missing instead of inventing it. Search can also use your own embeddings model. Partial What does not work yet: embeddings have been tested against a local test model only.

Checks that name the exact fix

check_composition runs on a draft before it is written. It flags raw colours and spacing, components rebuilt by hand, unlabelled form controls, two primary actions in one region, className or style overrides on system components and pattern rule breaks, and says what to use instead. audit_code runs on existing files and reports uses of deprecated items with their replacement and removal date. Working

check_composition → error  loom.accessibility  <input> has no label. Use TextField with a label.
                  → warn   loom.raw-colour     #2563eb. Use color.action.primary.
                  → warn   loom.reimplementation  Local Modal. Use Dialog from @your/system.

Illustrative output. The rule names are Loom's built-in rules.

Rules as files, severity per area

Governance lives in a loom/ folder in your repository: rules with an owner, rationale, severity and scope; exceptions with an expiry date; proposals; patterns; deprecations. Raw values and rebuilds are warnings by default; the owner of an area promotes them to errors for that area. Accessibility rules are always errors. Your ESLint config can be imported as rule records; ESLint still enforces those rules. Working

The rules command checks the same rules from the command line and exits with an error code when an error remains, so it can run in any CI from a copy of the repository. Partial What does not work yet: a packaged command line with a ready-made CI step. Concept

Typed tokens and one contract per component

Conversion writes your design decisions as DTCG 2025.10 tokens with the evidence for each value, and keeps your token names. Partial What does not work yet: the component tier and modes are in development; build outputs for CSS variables, Tailwind and CSS-in-JS are planned.

Each component gets a contract: purpose, anatomy, API, states, behaviour, accessibility and content, with every gap marked as a question. Working

React and React Native from one contract

Loom writes a component for React (on React Aria Components) and React Native from one contract, with generated tests for both: axe on every web state, the accessibility tree on every native state, keyboard scripts and parity scenarios. Partial What does not work yet: today this covers Button, Switch and Dialog; native tests run in Jest, not on devices; generating your own components this way is next. Partial

Version bumps decided by the contract

Loom compares two contract versions and decides the bump. Removed or renamed properties, a property becoming required, narrowed values, changed defaults and removed states or keyboard behaviour are major. Additions are minor. A visual change above 0.5% of a key screen is breaking too. Working

Deprecations name a replacement and a date, and the audit reports every remaining use. Partial What does not work yet: migration pull requests and codemods. Concept

Pull requests, not direct writes

Loom opens labelled draft pull requests through one interface for GitHub, GitLab and Azure DevOps, with the provenance of each change. Loom never merges. Partial What does not work yet: tested against simulated hosts only.

The look-diff command compares before and after screenshots against the 0.5% rule and marks each change in colour. Partial What does not work yet: capturing screenshots for every route automatically.

The check names the fix

This is real output from the recorded session on Loom Base, our own design system: the check names each problem in the first draft with its fix, and the second draft passes.

One draft, checked twice

Recorded server output

Off-system: 3 errors, 2 warnings across 6 nodes.

  • [warning] Text has style set: overriding a system component's styling is drift from its contract. → Customise through the component's slots, variants or overridable tokens wit…
  • [error] Button has no "variant" prop. → Did you mean "tone" ("primary" | "secondary" | "quiet" | "danger")? primary: the one main action in a view; secondary: other actions; q…
  • [error] <Modal> is not part of Loom Base (imported from "@mui/material"). → Closest system components: AILabel (ai-label), Dialog (dialog), Drawer (drawer). If none fits, draf…

On-system: 0 errors, 0 warnings across 5 nodes.

  • components: Card, Stack, Text, Button, ConfirmAction

The agent fixed what the check named, then checked again before handing the work over.

From the recorded agent session on Loom Base, our own design system (data/mcp-demo.json), read when this page is built.

Watch an agent build on the system

Recording of real output

A recorded session: a scripted agent against Loom Base, our own design system (MIT), with real server output. The replay types each call as the agent sent it and shows each result after its recorded latency, shortened for the page. Server output is in English.

Agent session · recorded session

  1. Agent

    I need a destructive "delete workspace" action with a confirmation. What does the system have?

  2. Loom · search_system · 145 ms→ search_system({"query":"delete confirm danger","kind":"component","limit":5})

    20 results for "delete confirm danger" (lexical ranking). Top: Dialog (dialog, 0.8, beta), ConfirmAction (confirm-action, 0.46, beta), ToastRegion (toast-region, 0.41, beta), Tag (tag, 0.27, beta), Alert (alert, 0.25, beta)

    • Dialog (dialog, strong, confidence 0.8) — import { Dialog } from "@loom/base/native";• ConfirmAction (confirm-action, partial, confidence 0.46) — import { ConfirmAction } from "@loom/base/native";• ToastRegion (toast-region, partial, confidence 0.41) — import { ToastRegion } from "@loom/base/native";
  3. Loom · get_entity · 34 ms→ get_entity({"id":"Button"})

    Button: Starts an action in the current view: save, publish, open a dialog, run a check.

  4. Agent

    Here is my first draft. It reaches for an MUI Modal, guesses a variant prop, uses two primary buttons and hard-codes a colour and a margin.

  5. Loom · check_composition · 48 ms→ check_composition({"tsx":"import { Card, Button, Heading, Text } from \"@loom/base/native\";\nim…"})

    Off-system: 3 errors, 2 warnings across 6 nodes.

    - [warning] Text has style set: overriding a system component's styling is drift from its contract. → Customise through the component's slots, variants or overridable tokens wit…- [error] Button has no "variant" prop. → Did you mean "tone" ("primary" | "secondary" | "quiet" | "danger")? primary: the one main action in a view; secondary: other actions; q…- [error] <Modal> is not part of Loom Base (imported from "@mui/material"). → Closest system components: AILabel (ai-label), Dialog (dialog), Drawer (drawer). If none fits, draf…
  6. Loom · check_composition · 5 ms→ check_composition({"tsx":"import { Card, Button, Text, Stack, ConfirmAction } from \"@loom/bas…"})

    On-system: 0 errors, 0 warnings across 5 nodes.

    components: Card, Stack, Text, Button, ConfirmAction
  7. Loom · audit_code · 117 ms→ audit_code({"files":[{"path":"src/settings/SettingsPage.tsx","source":"import React from \"react\";\nimport { Button as MuiButton, Dialog…

    Drift found: 11 errors, 11 warnings — 7 colours, 9 dimensions, 1 non-system import, 1 re-implemented component.

    ✗ src/settings/settings.css:2 Hard-coded colour #fffdf8 (background). → Use var(--base-color-palette-cotton-0) ().✗ src/settings/settings.css:5 Hard-coded dimension 16px (padding). → Use var(--base-space-scale-6) (16px).✗ src/settings/settings.css:5 Hard-coded dimension 20px (padding). → Use var(--base-space-scale-7) (20px).
  8. Loom · get_guidelines · 11 ms→ get_guidelines({"topic":"illustration"})

    Guidelines for illustration: missing. 0 sections. Missing: Loom Base has no dedicated illustration guideline. Do not invent one: follow existing foundations, and use propose_change (kind "guideline") to draft it for review.

    missing: Loom Base has no dedicated illustration guideline. Do not invent one: follow existing foundations, and use propose_change (kind "guideline") to draft it for review.
  9. Agent

    Research uploads need a drag-and-drop file area. Nothing in the system does that, so I will draft a proposal rather than ship a one-off.

  10. Loom · propose_change · 399 ms→ propose_change({"kind":"component","title":"FileDropzone","need":"Researchers attach interview recordings and notes to a journey. Set…","…

    Draft component-filedropzone-6af1d4b4 written to drafts/component-filedropzone-6af1d4b4.json (status: proposed, labelled AI-generated, not yet human-reviewed). Intake: extend. 7 existing alternatives listed; impact: 5 declared dependents.

    impact: 5 declared dependents (partial graph)status: proposed; system source modified: false
  11. Loom · check_composition · 69 ms→ check_composition({"tsx":"import { Modal } from \"@mui/material\";\nimport { Button } from \"@loo…"})

    Off-system: 4 errors, 5 warnings across 6 nodes.

    - [warning] Raw <h1> used where the system provides Heading. → import { Heading } from "@loom/base/native";- [error] Button has no "variant" prop. → Did you mean "tone" ("primary" | "secondary" | "quiet" | "danger")? primary: the one main action in a view; secondary: other actions; q…- [error] Button has no "variant" prop. → Did you mean "tone" ("primary" | "secondary" | "quiet" | "danger")? primary: the one main action in a view; secondary: other actions; q…
  12. Agent

    The booking flow needs a date range picker. Does the system have one?

  13. Loom · search_system · 17 ms→ search_system({"query":"date range picker for booking","kind":"component","limit":5})

    No component matches "date range picker". Closest partial matches: ComboBox (combo-box, partial match, confidence 0.24, beta: covers "picker" but not "date range"); FormField (form-field, partial match, confidence 0.24, beta: covers "picker" but not "date range"); LooseThreads (loose-threads, partial match, confidence 0.24, beta: covers "picker" but not "date range"). Logged as an unmet need. If this is a real gap, draft it with propose_change instead of building it off-system.

    • ComboBox — partial match: covers "picker" but not "date range"• FormField — partial match: covers "picker" but not "date range"• LooseThreads — partial match: covers "picker" but not "date range"
  14. Loom · search_system · 14 ms→ search_system({"query":"show an error when payment fails","kind":"component","limit":3})

    51 results for "show an error when payment fails" (lexical ranking). Top: ErrorSummary (error-summary, 0.93, beta), AIStatus (ai-status, 0.74, beta), Alert (alert, 0.74, beta)

    • ErrorSummary (error-summary, strong, confidence 0.93) — import { ErrorSummary } from "@loom/base/native";• AIStatus (ai-status, strong, confidence 0.74) — import { AIStatus } from "@loom/base/native";• Alert (alert, strong, confidence 0.74) — import { Alert } from "@loom/base/native";
  15. Loom · list_unmet_needs · 5 ms→ list_unmet_needs({})

    1 unmet need from 1 request; 0 over the threshold (3 requests from 2+ requesters in 30 days). "date range picker" x1.

    • "date range picker": 1 request(s), 1 requester(s) — Below threshold: needs 2 more request(s) and 1 more client(s) or team(s) within 30 days.

From an agent's draft to a merged change

Diagram
  1. Agent draftsa screen or a component
  2. Checkcheck_composition
  3. Fixuntil the check passes
  4. Pull requestlabelled loom:agent-generated
  5. CI runs the rules Conceptthe rules command
  6. A person reviews and mergesnothing merges without them
The CI step runs today as a command; a packaged CI step is a concept.

We show our own numbers

  • 50 of 50 parity scenarios pass, 28 on web and 22 on native, for Button, Switch and Dialog generated from one contract each, with a mutation check that confirms the tests fail when behaviour breaks. Partial Native tests do not yet run on devices.
  • The audit runs on code it was never tuned for. It scored a package it had never seen in 2.4 seconds, across all eight areas, with no configuration. Working
  • Two pilots made the engine better. On 25 October 2026, two pilots on real, private apps found gaps in how the audit reads non-standard apps: generated files counted twice, token modules missed, utility-class values not counted. Each fix is merged with tests.

The fastest code is the code an agent does not have to rewrite. Give it the real system.

Questions

Which stacks do you support? The audit reads React, TypeScript, CSS and plain HTML, CSS and JavaScript apps. Loom writes React and React Native. Styling output is CSS variables today; Tailwind and CSS-in-JS adapters are planned.

Does the MCP server need network access? No. It runs locally over standard input and output or local HTTP. Over a network it requires a token of at least 32 characters and refuses to start without one. Working

Can repository text hijack the agent? Tool results fence and neutralise content from your repository and mark it as data, not instructions. This lowers prompt-injection risk; it does not remove it. Working

How do I stop an agent run from costing too much? Cap it by calls, tokens or estimated cost. Every call is logged. Working

Will Loom merge into main? No. Loom opens draft pull requests. Your branch protection stays in force.

Start with a conversation about your system.

We read your system, show you what we found, and agree with your team what to fix first.