Skip to content
onsystemv0.6.0

Benchmark · 2026-10-12

Coding agents with and without onsystem

In the run of 2026-10-12, design-system rule violations per run went from 0.85 to 0.00 with onsystem's skill, MCP server and hook, with no change in tsc pass rate or cost per run (1,200 runs, 6 models, 3 providers, 5 repositories).

Each model wrote the same 20 UI components in five open-source repositories, once with no onsystem and once with each of its parts: the skill, the MCP server, the hook, and all three. A rule violation is an error from onsystem check that the run added to the repository. tsc type-checks each file in its project's own setup, and a sample was labelled blind by an AI reviewer.

No onsystem → skill, MCP server and hook, all 6 models pooled, with 95% intervals.

Rule violations per run
0.85→0.00
difference −0.85 [−1.38, −0.40]; without onsystem [0.40, 1.38]
tsc passes
93%→92%
difference 0 points [−4, +3]
Cost per run
$0.0464→$0.0454
without onsystem [$0.0432, $0.0497]

Results

Every arm, pooled over the six models. Rates have 95% Wilson intervals; means and differences have 95% intervals from a bootstrap that resamples tasks. Pooled by tier, rule violations per run went from 0.92 to 0.00 for the small models and from 0.62 to 0.00 for the mid models.

Results by arm, pooled over all 6 models, with 95% intervals
ArmRunsRule violations / runonsystem cleantsc passesCost / runHook blocks / runMCP calls / run
noneNo onsystem2400.85 [0.40, 1.38]64% 153/240 [57%, 70%]93% 222/240 [88%, 95%]$0.04640.000.0
skillSkill2400.42 [0.17, 0.72]76% 183/240 [70%, 81%]91% 218/240 [87%, 94%]$0.04860.000.0
mcpMCP server2400.01 [0.00, 0.04]93% 224/240 [89%, 96%]91% 218/240 [87%, 94%]$0.04670.007.6
hookHook2400.05 [0.01, 0.10]93% 223/240 [89%, 96%]92% 220/240 [87%, 95%]$0.05080.350.0
allSkill, MCP server and hook2400.00 [0.00, 0.00]95% 229/240 [92%, 97%]92% 221/240 [88%, 95%]$0.04540.117.5

Each arm against no onsystem

Paired on model, task and repeat. Accepted means the file was created, passes tsc and has no onsystem error.

Each arm against no onsystem, paired on model, task and repeat, all models, with 95% intervals
ArmPairsΔ rule violations / runΔ onsystem clean (points)Δ tsc passes (points)Δ accepted (points)
skillSkill240−0.42 [−0.69, −0.20]+13 [+5, +20]−2 [−5, +1]+12 [+4, +20]
mcpMCP server240−0.83 [−1.35, −0.40]+30 [+15, +45]−2 [−5, +2]+28 [+14, +42]
hookHook240−0.80 [−1.29, −0.38]+29 [+16, +44]−1 [−4, +2]+27 [+14, +41]
allSkill, MCP server and hook240−0.85 [−1.38, −0.40]+32 [+17, +48]0 [−4, +3]+29 [+15, +44]
Every model and arm (45 rows)Show
Results for every model and arm, then each tier and all models pooled, with 95% intervals
ModelArmRunsRule violations / runonsystem cleantsc passesAcceptedCreatedCost / runCalls / runHook blocks / runMCP calls / run
Claude Haiku 5.5none600.58 [0.20, 1.07]70% 42/60 [57%, 80%]97% 58/60 [89%, 99%]68% 41/60 [56%, 79%]100%$0.00416.20.000.0
Claude Haiku 5.5skill600.13 [0.02, 0.27]88% 53/60 [78%, 94%]100% 60/60 [94%, 100%]88% 53/60 [78%, 94%]100%$0.00458.00.000.0
Claude Haiku 5.5mcp600.05 [0.00, 0.15]98% 59/60 [91%, 100%]93% 56/60 [84%, 97%]92% 55/60 [82%, 96%]100%$0.00456.50.007.5
Claude Haiku 5.5hook600.12 [0.00, 0.32]95% 57/60 [86%, 98%]98% 59/60 [91%, 100%]93% 56/60 [84%, 97%]100%$0.00446.80.320.0
Claude Haiku 5.5all600.00 [0.00, 0.00]100% 60/60 [94%, 100%]97% 58/60 [89%, 99%]97% 58/60 [89%, 99%]100%$0.00415.90.057.0
Claude Sonnet 5.5none200.40 [0.00, 0.90]85% 17/20 [64%, 95%]100% 20/20 [84%, 100%]85% 17/20 [64%, 95%]100%$0.03755.30.000.0
Claude Sonnet 5.5skill200.15 [0.00, 0.40]90% 18/20 [70%, 97%]100% 20/20 [84%, 100%]90% 18/20 [70%, 97%]100%$0.04766.40.000.0
Claude Sonnet 5.5mcp200.00 [0.00, 0.00]100% 20/20 [84%, 100%]95% 19/20 [76%, 99%]95% 19/20 [76%, 99%]100%$0.04034.60.003.5
Claude Sonnet 5.5hook200.00 [0.00, 0.00]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100%$0.03945.50.200.0
Claude Sonnet 5.5all200.00 [0.00, 0.00]100% 20/20 [84%, 100%]95% 19/20 [76%, 99%]95% 19/20 [76%, 99%]100%$0.04254.20.104.0
Gemini 3.5 Flash-Litenone601.48 [0.70, 2.38]60% 36/60 [47%, 71%]95% 57/60 [86%, 98%]58% 35/60 [46%, 70%]100%$0.060722.10.000.0
Gemini 3.5 Flash-Liteskill601.07 [0.40, 1.82]68% 41/60 [56%, 79%]93% 56/60 [84%, 97%]65% 39/60 [52%, 76%]98%$0.061722.90.000.0
Gemini 3.5 Flash-Litemcp600.00 [0.00, 0.00]97% 58/60 [89%, 99%]93% 56/60 [84%, 97%]93% 56/60 [84%, 97%]97%$0.063824.40.006.3
Gemini 3.5 Flash-Litehook600.03 [0.00, 0.10]98% 59/60 [91%, 100%]93% 56/60 [84%, 97%]93% 56/60 [84%, 97%]100%$0.071025.10.480.0
Gemini 3.5 Flash-Liteall600.00 [0.00, 0.00]100% 60/60 [94%, 100%]93% 56/60 [84%, 97%]93% 56/60 [84%, 97%]100%$0.057522.40.325.6
Gemini 3.8 Flashnone200.90 [0.05, 1.90]15% 3/20 [5%, 36%]35% 7/20 [18%, 57%]15% 3/20 [5%, 36%]35%$0.271637.30.000.0
Gemini 3.8 Flashskill200.40 [0.00, 1.20]15% 3/20 [5%, 36%]20% 4/20 [8%, 42%]15% 3/20 [5%, 36%]20%$0.272438.10.000.0
Gemini 3.8 Flashmcp200.00 [0.00, 0.00]35% 7/20 [18%, 57%]35% 7/20 [18%, 57%]35% 7/20 [18%, 57%]35%$0.253738.00.0010.5
Gemini 3.8 Flashhook200.10 [0.00, 0.30]35% 7/20 [18%, 57%]30% 6/20 [15%, 52%]25% 5/20 [11%, 47%]40%$0.289138.60.250.0
Gemini 3.8 Flashall200.00 [0.00, 0.00]45% 9/20 [26%, 66%]40% 8/20 [22%, 61%]40% 8/20 [22%, 61%]45%$0.256739.00.0011.0
GPT-6 Lunanone600.70 [0.20, 1.32]68% 41/60 [56%, 79%]100% 60/60 [94%, 100%]68% 41/60 [56%, 79%]100%$0.00326.40.000.0
GPT-6 Lunaskill600.32 [0.10, 0.57]80% 48/60 [68%, 88%]97% 58/60 [89%, 99%]77% 46/60 [65%, 86%]100%$0.00397.30.000.0
GPT-6 Lunamcp600.00 [0.00, 0.00]100% 60/60 [94%, 100%]100% 60/60 [94%, 100%]100% 60/60 [94%, 100%]100%$0.00438.70.009.3
GPT-6 Lunahook600.00 [0.00, 0.00]100% 60/60 [94%, 100%]100% 60/60 [94%, 100%]100% 60/60 [94%, 100%]100%$0.00367.20.350.0
GPT-6 Lunaall600.00 [0.00, 0.00]100% 60/60 [94%, 100%]100% 60/60 [94%, 100%]100% 60/60 [94%, 100%]100%$0.00378.90.039.5
GPT-6.1 Solnone200.55 [0.15, 1.15]70% 14/20 [48%, 85%]100% 20/20 [84%, 100%]70% 14/20 [48%, 85%]100%$0.04335.50.000.0
GPT-6.1 Solskill200.00 [0.00, 0.00]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100%$0.05336.30.000.0
GPT-6.1 Solmcp200.00 [0.00, 0.00]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100%$0.04866.30.008.2
GPT-6.1 Solhook200.00 [0.00, 0.00]100% 20/20 [84%, 100%]95% 19/20 [76%, 99%]95% 19/20 [76%, 99%]100%$0.04426.20.250.0
GPT-6.1 Solall200.00 [0.00, 0.00]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100% 20/20 [84%, 100%]100%$0.04916.50.008.8
mid modelsnone600.62 [0.12, 1.25]57% 34/60 [44%, 68%]78% 47/60 [66%, 87%]57% 34/60 [44%, 68%]78%$0.117516.00.000.0
mid modelsskill600.18 [0.00, 0.52]68% 41/60 [56%, 79%]73% 44/60 [61%, 83%]68% 41/60 [56%, 79%]73%$0.124417.00.000.0
mid modelsmcp600.00 [0.00, 0.00]78% 47/60 [66%, 87%]77% 46/60 [65%, 86%]77% 46/60 [65%, 86%]78%$0.114216.30.007.4
mid modelshook600.03 [0.00, 0.10]78% 47/60 [66%, 87%]75% 45/60 [63%, 84%]73% 44/60 [61%, 83%]80%$0.124216.80.230.0
mid modelsall600.00 [0.00, 0.00]82% 49/60 [70%, 89%]78% 47/60 [66%, 87%]78% 47/60 [66%, 87%]82%$0.116116.60.037.9
small modelsnone1800.92 [0.46, 1.46]66% 119/180 [59%, 73%]97% 175/180 [94%, 99%]65% 117/180 [58%, 72%]100%$0.022711.60.000.0
small modelsskill1800.51 [0.22, 0.83]79% 142/180 [72%, 84%]97% 174/180 [93%, 98%]77% 138/180 [70%, 82%]99%$0.023312.70.000.0
small modelsmcp1800.02 [0.00, 0.05]98% 177/180 [95%, 99%]96% 172/180 [91%, 98%]95% 171/180 [91%, 97%]99%$0.024213.20.007.7
small modelshook1800.05 [0.00, 0.12]98% 176/180 [94%, 99%]97% 175/180 [94%, 99%]96% 172/180 [91%, 98%]100%$0.026313.10.380.0
small modelsall1800.00 [0.00, 0.00]100% 180/180 [98%, 100%]97% 174/180 [93%, 98%]97% 174/180 [93%, 98%]100%$0.021812.40.137.3
all modelsnone2400.85 [0.40, 1.38]64% 153/240 [57%, 70%]93% 222/240 [88%, 95%]63% 151/240 [57%, 69%]95%$0.046412.70.000.0
all modelsskill2400.42 [0.17, 0.72]76% 183/240 [70%, 81%]91% 218/240 [87%, 94%]75% 179/240 [69%, 80%]93%$0.048613.80.000.0
all modelsmcp2400.01 [0.00, 0.04]93% 224/240 [89%, 96%]91% 218/240 [87%, 94%]90% 217/240 [86%, 94%]94%$0.046714.00.007.6
all modelshook2400.05 [0.01, 0.10]93% 223/240 [89%, 96%]92% 220/240 [87%, 95%]90% 216/240 [86%, 93%]95%$0.050814.00.350.0
all modelsall2400.00 [0.00, 0.00]95% 229/240 [92%, 97%]92% 221/240 [88%, 95%]92% 221/240 [88%, 95%]95%$0.045413.40.117.5
Each arm against no onsystem, by model (36 rows)Show
Each arm against no onsystem for every model, each tier and all models, with 95% intervals
ModelArmPairsΔ rule violations / runΔ onsystem clean (points)Δ tsc passes (points)Δ accepted (points)
Claude Haiku 5.5skill60−0.45 [−0.82, −0.15]+18 [+8, +30]+3 [0, +10]+20 [+8, +33]
Claude Haiku 5.5mcp60−0.53 [−0.95, −0.18]+28 [+13, +45]−3 [−8, 0]+23 [+10, +38]
Claude Haiku 5.5hook60−0.47 [−0.80, −0.18]+25 [+12, +40]+2 [0, +5]+25 [+12, +40]
Claude Haiku 5.5all60−0.58 [−1.07, −0.20]+30 [+13, +48]0 [0, 0]+28 [+12, +47]
Claude Sonnet 5.5skill20−0.25 [−0.65, 0.00]+5 [0, +15]0 [0, 0]+5 [0, +15]
Claude Sonnet 5.5mcp20−0.40 [−0.90, 0.00]+15 [0, +30]−5 [−15, 0]+10 [−10, +30]
Claude Sonnet 5.5hook20−0.40 [−0.90, 0.00]+15 [0, +30]0 [0, 0]+15 [0, +30]
Claude Sonnet 5.5all20−0.40 [−0.90, 0.00]+15 [0, +30]−5 [−15, 0]+10 [−10, +30]
Gemini 3.5 Flash-Liteskill60−0.42 [−1.07, +0.02]+8 [+2, +17]−2 [−8, +5]+7 [0, +15]
Gemini 3.5 Flash-Litemcp60−1.48 [−2.38, −0.70]+37 [+20, +55]−2 [−12, +7]+35 [+18, +53]
Gemini 3.5 Flash-Litehook60−1.45 [−2.37, −0.67]+38 [+20, +57]−2 [−10, +7]+35 [+18, +52]
Gemini 3.5 Flash-Liteall60−1.48 [−2.38, −0.70]+40 [+22, +58]−2 [−13, +8]+35 [+15, +55]
Gemini 3.8 Flashskill20−0.50 [−1.40, +0.15]0 [−20, +20]−15 [−35, +5]0 [−20, +20]
Gemini 3.8 Flashmcp20−0.90 [−1.90, −0.05]+20 [−5, +45]0 [−25, +25]+20 [−5, +45]
Gemini 3.8 Flashhook20−0.80 [−1.85, +0.05]+20 [0, +45]−5 [−25, +15]+10 [−15, +35]
Gemini 3.8 Flashall20−0.90 [−1.90, −0.05]+30 [+5, +55]+5 [−15, +25]+25 [0, +50]
GPT-6 Lunaskill60−0.38 [−0.85, 0.00]+12 [−7, +30]−3 [−10, 0]+8 [−12, +28]
GPT-6 Lunamcp60−0.70 [−1.32, −0.20]+32 [+13, +52]0 [0, 0]+32 [+13, +52]
GPT-6 Lunahook60−0.70 [−1.32, −0.20]+32 [+13, +52]0 [0, 0]+32 [+13, +52]
GPT-6 Lunaall60−0.70 [−1.32, −0.20]+32 [+13, +52]0 [0, 0]+32 [+13, +52]
GPT-6.1 Solskill20−0.55 [−1.15, −0.15]+30 [+10, +50]0 [0, 0]+30 [+10, +50]
GPT-6.1 Solmcp20−0.55 [−1.15, −0.15]+30 [+10, +50]0 [0, 0]+30 [+10, +50]
GPT-6.1 Solhook20−0.55 [−1.15, −0.15]+30 [+10, +50]−5 [−15, 0]+25 [+10, +45]
GPT-6.1 Solall20−0.55 [−1.15, −0.15]+30 [+10, +50]0 [0, 0]+30 [+10, +50]
mid modelsskill60−0.43 [−0.82, −0.12]+12 [0, +23]−5 [−12, +2]+12 [0, +23]
mid modelsmcp60−0.62 [−1.25, −0.12]+22 [+7, +38]−2 [−10, +7]+20 [+3, +37]
mid modelshook60−0.58 [−1.23, −0.10]+22 [+7, +37]−3 [−10, +3]+17 [+2, +32]
mid modelsall60−0.62 [−1.25, −0.12]+25 [+10, +42]0 [−8, +8]+22 [+7, +38]
small modelsskill180−0.42 [−0.69, −0.18]+13 [+3, +22]−1 [−4, +3]+12 [+2, +22]
small modelsmcp180−0.91 [−1.42, −0.46]+32 [+17, +48]−2 [−6, +2]+30 [+16, +45]
small modelshook180−0.87 [−1.35, −0.45]+32 [+17, +47]0 [−3, +3]+31 [+17, +46]
small modelsall180−0.92 [−1.46, −0.46]+34 [+18, +51]−1 [−4, +3]+32 [+16, +48]
all modelsskill240−0.42 [−0.69, −0.20]+13 [+5, +20]−2 [−5, +1]+12 [+4, +20]
all modelsmcp240−0.83 [−1.35, −0.40]+30 [+15, +45]−2 [−5, +2]+28 [+14, +42]
all modelshook240−0.80 [−1.29, −0.38]+29 [+16, +44]−1 [−4, +2]+27 [+14, +41]
all modelsall240−0.85 [−1.38, −0.40]+32 [+17, +48]0 [−4, +3]+29 [+15, +44]

From results.json and results.md; one row per run in runs.csv.

Blind labels

A stratified random sample of 60 files (12 per arm) was labelled blind by an AI reviewer (Claude Opus 5.5). It saw the task, the design system and the file, not the model, the arm or onsystem's verdict, and counted invented APIs, hardcoded values, native elements and unnamed controls. Two of the six models (Claude Haiku 5.5 and Claude Sonnet 5.5) are from the same family as the labeller, so they may share its blind spots. With 12 files per arm, the intervals are wide. Who labelled, what it could see and the rules it applied are in LABELLER.md.

The blind-labelled sample by arm, as the labeller counted it, with 95% intervals
ArmFiles labelledNo violations (labeller)Violations / file (labeller)Would merge (labeller)
noneNo onsystem1283% 10/12 [55%, 95%]0.42 [0.00, 1.20]92% 11/12 [65%, 99%]
skillSkill12100% 12/12 [76%, 100%]0.00 [0.00, 0.00]100% 12/12 [76%, 100%]
mcpMCP server12100% 12/12 [76%, 100%]0.00 [0.00, 0.00]100% 12/12 [76%, 100%]
hookHook1283% 10/12 [55%, 95%]0.67 [0.00, 2.15]100% 12/12 [76%, 100%]
allSkill, MCP server and hook1283% 10/12 [55%, 95%]0.92 [0.00, 2.54]92% 11/12 [65%, 99%]

Per file, onsystem and the labeller both found a problem in 1, only onsystem in 3, only the labeller in 5 and neither in 51.

Of onsystem's 22 findings on these files, the labeller judged 3 correct, 0 wrong and 19 debatable.

Method

The tasks

20 tasks, 4 per repository, each a new component file a product team would write. Each names something the design system has to answer: a status color, a destructive action, an icon-only button, a dialog, a form control. Every prompt asks the agent to use the project's components and tokens, to create only that file and to run nothing.

The 20 promptsShow
  • chatbot

    • Create components/chat/model-settings-card.tsx exporting ModelSettingsCard: a card with a title, a description, an API key text input, a Save button and a small green 'Connected' status label.
    • Create components/chat/rate-limit-alert.tsx exporting RateLimitAlert: an inline red error alert telling the user they were rate limited, showing when they can retry, with a 'Try again' button.
    • Create components/chat/message-toolbar.tsx exporting MessageToolbar: a row of icon-only buttons under an assistant message: copy, regenerate, thumbs up and thumbs down.
    • Create components/chat/share-chat-dialog.tsx exporting ShareChatDialog: a dialog showing the share link in a read-only input, with an icon-only copy button next to it and a secondary 'Make private' action.
  • dub

    • Create apps/web/ui/links/link-expiry-banner.tsx exporting LinkExpiryBanner: a dismissible warning banner shown when a short link expires within 24 hours, with an 'Extend expiration' button and an icon-only close button.
    • Create apps/web/ui/workspaces/delete-workspace-modal.tsx exporting DeleteWorkspaceModal: a confirmation modal for deleting a workspace that asks the user to type the workspace slug to confirm, with a Cancel button and a red, destructive 'Delete workspace' button.
    • Create apps/web/ui/analytics/stat-card.tsx exporting StatCard: a small card with a metric label, a large number, and a change indicator that is green for an increase and red for a decrease, with an info icon that shows a tooltip explaining the metric.
    • Create apps/web/ui/links/copy-short-link.tsx exporting CopyShortLink: a short link shown in a read-only field next to an icon-only copy button, which shows a 'Copied' state for two seconds after copying.
  • saas-starter

    • Create app/(dashboard)/dashboard/billing/plan-summary-card.tsx exporting PlanSummaryCard: a card showing the current plan name, the monthly price, the next renewal date, a small green 'Active' status label and a 'Manage subscription' button.
    • Create app/(dashboard)/dashboard/team/invite-member-form.tsx exporting InviteMemberForm: a form to invite a team member, with a labelled email input, a choice between the Member and Owner roles as radio buttons, and an 'Invite member' submit button.
    • Create app/(dashboard)/dashboard/settings/danger-zone-card.tsx exporting DangerZoneCard: a card with a red 'Delete team' heading, a short warning that this removes every member and all data, and a destructive 'Delete team' button.
    • Create components/user-menu.tsx exporting UserMenu: a round avatar button showing the user's initials that opens a dropdown menu with 'Settings', 'Billing' and 'Sign out' items, with 'Sign out' shown in red.
  • openstatus

    • Create apps/dashboard/src/components/monitors/monitor-status-card.tsx exporting MonitorStatusCard: a card for one uptime monitor with its name, its URL, a status label that is green when up, red when down and yellow when degraded, the time of the last check, and a 'Pause' button.
    • Create apps/dashboard/src/components/incidents/resolve-incident-dialog.tsx exporting ResolveIncidentDialog: a dialog to resolve an incident, with a textarea for the final update, a Cancel button and a 'Resolve incident' button.
    • Create apps/dashboard/src/components/status-pages/subscribers-empty-state.tsx exporting SubscribersEmptyState: an empty state for a status page without subscribers, with an icon, a title, a short description and an 'Add subscriber' button.
    • Create apps/dashboard/src/components/monitors/region-select.tsx exporting RegionSelect: a labelled select to choose a monitoring region (Amsterdam, Frankfurt, Virginia, Singapore, Sydney), with helper text under it.
  • shadcn-website

    • Create apps/v4/components/feedback-widget.tsx exporting FeedbackWidget: a small card asking 'Was this page helpful?' with icon-only thumbs-up and thumbs-down buttons; after a vote, a textarea for optional comments and a 'Send feedback' button appear.
    • Create apps/v4/components/release-badges.tsx exporting ReleaseBadges: a list of component names, each with a status label: 'New' in green, 'Updated' in blue and 'Deprecated' in amber.
    • Create apps/v4/components/newsletter-signup.tsx exporting NewsletterSignup: an inline form with an email input and a 'Subscribe' button, muted helper text under it, and a success message in place of the form once it is submitted.
    • Create apps/v4/components/shortcuts-table.tsx exporting ShortcutsTable: a table of keyboard shortcuts with an 'Action' column and a 'Keys' column that shows each key as a small styled key label (Search ⌘K, Toggle theme ⌘J, Copy code ⌘C).

The repositories

RepositoryCommitAppDesign systemTypeScript
vercel/chatbotc2f8235.shadcn/ui in components/ui: 23 components, 31 color tokens. The v1 benchmark's repository.5.8.2
dubinc/dub70c96f9apps/webWorkspace package @dub/ui, a custom (not shadcn) system: 430 components and icons, no color tokens found, so the color rules stay quiet here and the component, prop and variant rules do the work.5.4.4
nextjs/saas-starter6e33e58.shadcn/ui in components/ui: 7 components, 40 color tokens. Next.js, Tailwind v4.5.8.3
openstatusHQ/openstatuse36446dapps/dashboardWorkspace package @openstatus/ui (shadcn-based) plus app components: 132 components, 51 color tokens. TypeScript 7.7.0.2
shadcn-ui/ui0132174apps/v4The shadcn/ui registry (registry/new-york-v4/ui): 68 components, 40 color tokens. The ui.shadcn.com site.5.9.2

The arms

Each arm adds shipped onsystem files to the same loop, unchanged:

  • none

    What the agent gets on top of the file tools
    Nothing. The prompt still asks it to use the project's components and tokens, as v1's did.
  • skill

    What the agent gets on top of the file tools
    The Claude Code plugin's SKILL.md, the way Agent Skills work: its name and description in the system prompt and a load_skill tool that returns the whole file. The skill names the MCP tools, which this arm does not have; its other instructions (tokens, variants, native elements, icon buttons) still apply.
  • mcp

    What the agent gets on top of the file tools
    The onsystem MCP server (this checkout's dist/cli.js), started over stdio in the run's copy of the repository. Its five tools, with their own descriptions and schemas, as mcp__onsystem__<tool>, and its instructions in the system prompt, as MCP clients do.
  • hook

    What the agent gets on top of the file tools
    The plugin's hook script run after every write or edit, with the PostToolUse input Claude Code gives it. On exit 2 its stderr goes back to the model with the tool result; on exit 0 its additionalContext is added as a note. Its message points at get_component and get_tokens, which this arm does not have.
  • all

    What the agent gets on top of the file tools
    All three: the Claude Code plugin's setup.

skill and hook each mention tools only the MCP arm has. That is how the files ship; the arms measure what each part does on its own, not a redesigned part.

The models

Six models from three providers (Anthropic, Google, OpenAI), in two tiers. Every model runs in the same agent loop, with the same file tools and no shell, at the same nominal reasoning level. Prices as read on 2026-10-12.

ModelIDProviderTierRuns
Claude Haiku 5.5claude-haiku-5-5Anthropicsmall300
Gemini 3.5 Flash-Litegemini-3.5-flash-liteGooglesmall300
GPT-6 Lunagpt-6-lunaOpenAIsmall300
Claude Sonnet 5.5claude-sonnet-5-5Anthropicmid100
Gemini 3.8 Flashgemini-3.8-flashGooglemid100
GPT-6.1 Solgpt-6.1-solOpenAImid100

Scoring

Three measures, two of them independent of onsystem:

  1. tsc (independent). Each changed TypeScript file is type-checked in its project's own setup: a temporary tsconfig extends the project's, lists only the changed files (plus the project's ambient .d.ts files), and runs with the repository's own TypeScript. Only diagnostics in the changed files count. Invented components, props and variant values are type errors when the design system is typed, which all five are; hardcoded colors and spacing are not. A run passes when its file exists and has no type error. dub's @dub/ui and @dub/utils export a build output that an install without scripts lacks, so their imports are mapped to their sources for tsc.

  2. onsystem check (the tool's own). The changed UI files, against a baseline of the untouched repository, so only findings the run added count. A run is clean when its file exists and has no error.

  3. Blind labels (independent of onsystem). A stratified random sample of files (2 per model and arm by default), blinded: the labeller, a person or an AI reviewer, recorded in labels/LABELLER.md, sees the task, the design system and the file, not the model, the arm or onsystem's verdict. They count invented APIs, hardcoded values, native elements and unnamed controls, and say whether they would merge it. Then, separately, they judge each onsystem finding on those files as correct, wrong or debatable. LABELLING.md is the guide.

prepare checks that the scorers can be trusted in each repository before anything runs: a correct use of the design system's Button must pass both tsc and onsystem, the same component with an invented variant (variant="warning") must fail both, and two or three existing files must type-check. All five pass (tsc: 0 errors on the correct use, 1 on the invented one; onsystem: 0 and 1, no-unknown-variant).

Also recorded per run: whether it changed files other than its own, requests, tokens, cost, time, hook blocks, MCP calls and skill loads.

Statistics

Outcomes, fixed before any run:

  • Primary, independent: tsc pass rate per model and arm.

  • Primary, onsystem's own: onsystem-clean rate per model and arm.

  • Composite: accepted = file created, type-checks and onsystem-clean.

  • Blind labels: would-merge rate, files without violations, violations per file, and the share of onsystem's findings the labeller judges correct.

  • Secondary: onsystem errors per run, cost, requests, tokens, hook blocks, MCP calls, skill loads.

Rates get 95% Wilson score intervals. Means and every difference between arms get 95% percentile intervals from a cluster bootstrap that resamples tasks (10,000 draws, seed 1): runs of the same task are not independent, since a hard task is hard in every arm. Each arm is compared with none on the same model, task and repeat. There are no p-values. The tables pool the models of each tier, and all models together. The headline is the comparison pooled over a tier's models for each arm (4 per tier and outcome); per-model comparisons (up to 36) are reported too, and about 1 in 20 of those intervals would exclude zero by chance alone. The tables are computed by lib/stats.ts, which test/bench-v2.test.ts covers.

Cost and time

API cost
$57.08 for 1,200 runs, under a cap of $60
Wall-clock time
120 minutes, runs in parallel
Time per run
median 33 s, from 8 s to 427 s
How runs ended
1,122 finished, 78 stopped at a limit (max-calls); a run that stops at a limit counts, with what it wrote

Threats to validity

  • The labels are an AI reviewer's. The sample was labelled blind by an AI reviewer (Claude Opus 5.5), not by people. LABELLER.md records what it could see and how it decided.
  • The labeller's family is under test. Two of the six models (Claude Haiku 5.5 and Claude Sonnet 5.5) are from the same family as the labeller, so they may share blind spots.
  • onsystem is one of the scorers. The headline counts onsystem's own errors. tsc and the blind labels are the independent checks: tsc does not see hardcoded values, and the labels cover 60 files.
  • Not a product test. The loop is not Claude Code, Codex or Gemini CLI. Real clients add their own prompts and tools, which can change how much onsystem helps.

  • One reasoning level. medium means different things to different providers; other levels may change the picture.

  • The prompt asks for the design system. Every arm is told to use the project's components and tokens. A prompt that does not ask would likely show larger effects; this one is the conservative case, and the same as v1.

  • The scorers' blind spots. tsc sees invented APIs, not hardcoded values. onsystem sees both but is the tool under test; its precision on the corpus is in corpus/labels.json. Neither judges whether the UI does what was asked; only the blind labels do, and only on a sample.

  • Repositories and tasks. Five open-source React and Tailwind projects and twenty single-file tasks. Editing existing screens, other frameworks and multi-file work are not covered. The repositories and their tasks may be in some models' training data.

  • The skill and hook arms mention the MCP tools they do not include, as shipped.

  • Prices and models change. The cost table is for 2026-10-12. Gemini 3.8 Flash's price doubles on 2027-01-01, and Gemini 3.1 Pro is a preview.

  • Run-to-run variance is measured only where a matrix has repeats.

Reproduce it

Requirements: macOS or Linux, Node.js 22.18 or later (the harness's TypeScript runs through Node's type stripping; 24 is what CI uses), pnpm 10 or later and git on the path, about 8 GB of disk for the prepared repositories, and pnpm build in this checkout (the MCP server, the hook and the scoring all use dist/cli.js).

Shell
pnpm install && pnpm buildnode bench/v2/run.mjs plan                       # matrices, runs and cost; no network, no keynode bench/v2/run.mjs prepare                    # fetch, install (scripts off), baseline, scorer controlsnode bench/v2/run.mjs run --matrix mock --out /tmp/bench-mock   # offline, scripted model, no key

The work folder ($TMPDIR/onsystem-bench-v2, or --work, or BENCH_WORK_DIR) must sit outside any project, since onsystem looks upwards for node_modules and workspace roots.

Once the spend is approved, with ANTHROPIC_API_KEY, OPENAI_API_KEY and GEMINI_API_KEY exported in the shell:

Shell
node bench/v2/run.mjs run --matrix canary   --out bench/v2/results/canary   --max-cost 5node bench/v2/run.mjs plan --matrix standard --usage-from bench/v2/results/canarynode bench/v2/run.mjs run --matrix standard --out bench/v2/results/standard --max-cost 60 --concurrency 8node bench/v2/run.mjs labels --out bench/v2/results/standard   # then label, see LABELLING.mdnode bench/v2/run.mjs report --out bench/v2/results/standard   # rebuilds the tables, labels included

Keys are read from those environment variables only, by name, and never printed or written; a missing one stops the run before anything starts, naming the variable. Child processes (the MCP server, the hook, tsc, installs) never receive them. A matrix needs only its providers' keys; mock needs none.

run resumes: finished runs are skipped, and spending so far (ledger.jsonl) counts against the cap. v1's runs took 22 seconds to 6 minutes each; --concurrency sets how many run at once.

Every request's usage and every tool call are on the bench-v2-2026-10-12 release; the files each run wrote, its scores and the labels are in bench/v2/results/standard. The full method is in bench/v2/README.md.

The earlier benchmark

v1 was one repository, Claude only, 40 runs, and onsystem scored its own output. It showed a direction, not a result.

  • Agent

    v1 (2026-10-08)
    Claude Code headless, with the user's own settings loaded
    v2
    One neutral agent loop over each provider's API, nothing else loaded
  • Models

    v1 (2026-10-08)
    Claude Haiku 4.5, Claude Opus 5
    v2
    Small and mid tiers from Anthropic, OpenAI and Google; frontier tier optional
  • Repositories

    v1 (2026-10-08)
    1 (vercel/chatbot)
    v2
    5 from corpus/, pinned, installed with scripts off
  • Tasks

    v1 (2026-10-08)
    10
    v2
    20 (4 per repository), fixed prompts
  • Conditions

    v1 (2026-10-08)
    base, plugin
    v2
    none, skill, mcp, hook, all
  • Repeats

    v1 (2026-10-08)
    1
    v2
    1 to 3 per cell
  • Scoring

    v1 (2026-10-08)
    onsystem check
    v2
    tsc in each project's own setup, onsystem check, and blind labels on a stratified sample
  • Statistics

    v1 (2026-10-08)
    counts
    v2
    Wilson intervals for rates, cluster bootstrap for means and paired differences
Claude Haiku 4.520 runs

Components with no design-system errors

Without6 / 10
With plugin10 / 10

Design-system errors, all ten tasks

Without13
With plugin0
Cost
−11%
$1.31 → $1.17
Avg. time
46 → 42 s
Tool calls
5.6 per task
Claude Opus 520 runs

Components with no design-system errors

Without8 / 10
With plugin10 / 10

Design-system errors, all ten tasks

Without5
With plugin0
Cost
+7%
$7.74 → $8.31
Avg. time
108 → 141 s
Tool calls
7.3 per task
Every task, every runShow
Design-system errors per task, for each model, without and with the plugin
TaskHaiku 4.5withoutHaiku 4.5with pluginOpus 5withoutOpus 5with plugin
Usage bannera dismissible warning banner shown when the user is close to their monthly message limit ("You've used 90% of your messages this month"), with an Upgrade button and an icon-only close button.1 error1 × icon-button-accessible-namecleancleanclean
Delete dialoga confirmation dialog for deleting all chats, with a Cancel action and a red, destructive confirm action.cleancleancleanclean
Settings carda card with a title, a description, an API key text input, a Save button and a small green 'Connected' status label.2 errors2 × no-hardcoded-colorclean4 errors4 × no-hardcoded-colorclean
Empty historyan empty state for the chat history sidebar with an icon, a short message and a 'New chat' button.cleancleancleanclean
Message toolbara row of icon-only buttons under an assistant message: copy, regenerate, thumbs up and thumbs down.cleancleancleanclean
Upgrade carda pricing card for the Pro plan with the price, a list of four features with check icons, a 'Most popular' label and an 'Upgrade to Pro' button.cleancleancleanclean
Rate limit alertan inline red error alert telling the user they were rate limited, showing when they can retry, with a 'Try again' button.9 errors9 × no-hardcoded-colorcleancleanclean
Share dialoga dialog showing the share link in a read-only input, with an icon-only copy button next to it and a secondary 'Make private' action.1 error1 × prefer-design-system-componentcleancleanclean
Profile carda small card with the user's avatar initials in a colored circle, their name and email, a 'Free plan' label and a 'Sign out' button.cleanclean1 error1 × no-hardcoded-colorclean
Shortcuts dialoga dialog listing keyboard shortcuts (New chat ⌘K, Toggle sidebar ⌘B, Copy last answer ⌘⇧C) in two columns, with styled key labels.cleancleancleanclean

Claude Code built ten components for vercel/ai-chatbot, once as it ships and once with the plugin, and onsystem check scored every file. Without the plugin, the misses were raw colors for things the prompt described, a native <label> where the project has Label, and an icon button nobody could name with a screen reader. With it, the agent looked components and tokens up before writing (5.6 to 7.3 design-system tool calls per task).

It is one project and 40 runs, one per task, model and condition, so read it as a direction rather than a rate; Claude Haiku 4.5 cost less with the plugin than without. The method, per-run results and every generated file are in the repository.