← News

RESEARCH

The AI Cost of Marionette

Newspaper headline: Old Marionette yells at vibes. An elderly wooden marionette shakes its fist at a cloud labelled Vibes.
Old Marionette yells at vibes.

When we introduced Marionette 5, one reply summed up the general mood: ‘Everybody who knows what marionette is has stiff knees and creaky backs.’ Fair enough. By the vibes, Marionette lost a long time ago.

In that post we described how we decided whether Marionette was still worth investing in. The usual advice was about Backbone’s age, community size and hiring pools, and all of it assumed a human would write the code. So we asked the agents which ecosystem they’d want to work in. They liked Regions, predictable lifecycles and clear ownership. But that was the agents’ opinion, and opinions are vibes too.

Then Nolan Lawson asked, ‘Did anyone think to ask the agents?’ He had an agent build the same app in twelve stacks and measured what each one cost and shipped. Marionette wasn’t one of them.

So we ran his benchmark task with Codex, Marionette included, and published the results. Last time we asked the agents what they thought. This time we measured what they did.

What we ran.

The same small Pokémon app: listings, search, filters and detail pages. Four independent Codex builds in each of six stacks: Marionette, Vanilla, React Router, SvelteKit, Nuxt and Angular. This is our Codex cohort, not an extension of Nolan’s model results.

A small task gives us something concrete to repeat: the same requirements, four independently generated apps per stack, and a common acceptance suite. It also leaves room for a closer look at how each agent chose to organize the work.

Agents inherit the internet’s preferences along with its examples, good and bad. Popularity may influence what they reach for; this experiment does not measure their training data or prove that explanation. It measures what they built here.

We keep Nolan’s results order: cost, time, tokens, delivered quality and source size. Charts show all four applications, their median and their full range. Sort the chart or switch to its table to inspect the individual values.

01 / COST

What did the agent use?

Hypothetical API proxy per build

Calculated under Standard, short-context API assumptions. Not measured cost, subscription spending or an invoice. Actual dollar charges are unavailable. Assumptions and missing accounting →

Median · line: minimum–maximum · circles: b1–b4

Hypothetical API proxy per build (USD proxy). Four builds per framework; median (minimum–maximum).
FrameworkMedian (range)b1b2b3b4
Vanilla0.2512 (0.2465–0.2856)0.25540.24650.24700.2856
Marionette0.3107 (0.2887–0.3337)0.33110.29030.28870.3337
React Router0.3470 (0.2635–0.4943)0.49430.26350.32000.3740
Nuxt0.4150 (0.2875–0.4827)0.42150.28750.40850.4827
SvelteKit0.4171 (0.3862–0.4909)0.39830.43590.38620.4909
Angular0.4478 (0.3823–0.5413)0.51050.38500.54130.3823
All four independent builds. Range of calculated proxies under one hypothetical tariff scenario, not observed charges or an uncertainty interval.

At the stated token prices, Marionette’s median equivalent is about 31 cents. Vanilla is about 25 cents, React Router 35, SvelteKit and Nuxt 42, and Angular 45. This is a hypothetical token-only comparison, not what our subscription charged. The distinction matters more than the pennies.

What the dollar estimate does and does not mean

The calculation uses $2 per million uncached input tokens, $0.10 cached and $10 output, the previously verified Standard short-context tariff for the reported model. It assumes those rates apply; per-request context lengths, effective service tier, extra tool fees and separate child usage are not established. Actual subscription debits are unavailable. It is not an invoice.

Generation time per build

Median · line: minimum–maximum · circles: b1–b4

Generation time per build (minutes). Four builds per framework; median (minimum–maximum).
FrameworkMedian (range)b1b2b3b4
Vanilla4.66 (4.36–4.93)4.934.794.364.53
Marionette5.07 (4.12–5.57)5.574.124.845.30
React Router5.55 (4.45–6.55)6.554.454.826.27
SvelteKit6.08 (5.33–7.19)6.207.195.965.33
Nuxt6.92 (4.51–7.26)6.744.517.107.26
Angular6.97 (5.88–8.01)7.406.548.015.88
All four independent builds. Ranges describe observations; they are not confidence intervals.

Marionette’s median generation took 5.07 minutes, between Vanilla’s 4.66 and React Router’s 5.55. The full ranges overlap. The timer spans the native agent turn, including its tools and self-tests, but not setup, independent acceptance or Lighthouse. It does not establish a framework’s intrinsic development speed.

Tokens per build

Median · line: minimum–maximum · circles: b1–b4

Tokens per build (million tokens). Four builds per framework; median (minimum–maximum).
FrameworkMedian (range)b1b2b3b4
Vanilla0.961 (0.903–1.057)0.9870.9360.9031.057
Marionette1.118 (0.991–1.383)1.3830.9980.9911.238
React Router1.509 (0.924–1.859)1.8590.9241.2871.730
Angular1.681 (1.414–2.556)1.9211.4422.5561.414
SvelteKit1.819 (1.688–2.065)1.9412.0651.6981.688
Nuxt2.145 (1.113–2.542)2.2541.1132.0362.542
All four independent builds. Ranges describe observations; they are not confidence intervals.

Token totals are final cumulative root-thread input plus output. Cached input is already part of input and is not counted twice. Reasoning detail is not added again. A large cached context and fresh output carry different assumed prices, so token count and the dollar proxy are not interchangeable rankings.

02 / QUALITY

Four working apps.

All four Marionette applications passed all 54 independent acceptance cases. So did the retained peer applications. The comparison starts with working software: listing, searching, filtering and navigating the same Pokémon dataset.

Loading measurements

FrameworkCLSPerformanceAccessibility
Marionette0.11196100
Vanilla0.146592.5100
React Router098100
SvelteKit0100100
Nuxt099100
Angular0.10979100

Home JavaScript transferred

Median · line: minimum–maximum · circles: b1–b4

Home JavaScript transferred (KiB). Four builds per framework; median (minimum–maximum).
FrameworkMedian (range)b1b2b3b4
Vanilla4.48 (4.30–4.66)4.304.464.664.50
Marionette25.56 (23.75–25.73)25.6123.7525.5225.73
SvelteKit36.67 (36.48–36.85)36.8536.7136.4836.62
Angular76.72 (49.00–95.75)95.7575.9349.0077.52
Nuxt82.57 (76.01–82.69)76.0182.5882.6982.56
React Router115.66 (114.31–115.73)115.62114.31115.73115.69
All four independent builds. Ranges describe observations; they are not confidence intervals. Each build value is its saved median of five mobile home Lighthouse samples; Nuxt b3 uses the corrected measurement.

Marionette transferred a median 25.6 KiB of home JavaScript, smaller than the framework peers and larger than Vanilla.

Home Total Blocking Time (TBT)

Median · line: minimum–maximum · circles: b1–b4

Home Total Blocking Time (TBT) (ms). Four builds per framework; median (minimum–maximum).
FrameworkMedian (range)b1b2b3b4
SvelteKit0.000 (0.000–0.000)0.0000.0000.0000.000
Vanilla0.000 (0.000–224.000)0.000224.0000.0000.000
Marionette4.500 (1.000–13.000)1.0008.0001.00013.000
React Router5.500 (0.500–51.000)5.00051.0000.5006.000
Nuxt45.750 (8.000–78.000)8.00078.00060.50031.000
Angular870.428 (206.000–1,031.500)983.073757.783206.0001,031.500
All four independent builds. Ranges describe observations; they are not confidence intervals. Each build value is its saved median of five mobile home Lighthouse samples; Nuxt b3 uses the corrected measurement.

Home Largest Contentful Paint (LCP)

Median · line: minimum–maximum · circles: b1–b4

Home Largest Contentful Paint (LCP) (ms). Four builds per framework; median (minimum–maximum).
FrameworkMedian (range)b1b2b3b4
Vanilla789.801 (773.069–848.322)805.136848.322773.069774.466
Angular1,646.265 (1,438.886–1,989.485)1,545.7821,746.7481,989.4851,438.886
SvelteKit1,685.932 (1,223.039–1,693.380)1,223.0391,693.3801,683.1421,688.722
Marionette1,883.280 (1,864.987–1,893.796)1,893.7961,884.7871,864.9871,881.773
Nuxt2,003.539 (1,849.128–2,086.339)2,086.3391,849.1281,985.0762,022.003
React Router2,197.548 (2,117.151–2,236.093)2,230.3302,164.7662,117.1512,236.093
All four independent builds. Ranges describe observations; they are not confidence intervals. Each build value is its saved median of five mobile home Lighthouse samples; Nuxt b3 uses the corrected measurement.

Home performance was 96 [92–97]; every route’s automated accessibility median was 100. Those scores are not a complete accessibility audit. Home CLS was 0.111. These charts cover home loading. All six routes are retained in the downloadable data.

All six routes.

Median [minimum–maximum] across four application medians. Each application contributes five measurements per route. The warm-server/cold-browser warning above applies throughout these loading comparisons.

Performance score

FrameworkHomeFiltered homePokémonMoveAbilityType
Marionette96 [92–97]96 [92–97]75 [74–81]96 [95–97]96.5 [96–97]96.5 [96–97]
Vanilla92.5 [91–97]94.5 [91–97]94 [82–96]96.5 [95–97]97 [97–97]97 [97–97]
React Router98 [97–99]98.5 [98–99]87 [67–90]99.5 [98–100]99 [99–99]99 [99–99]
SvelteKit100 [100–100]100 [100–100]98 [72–98]99 [99–100]100 [100–100]100 [100–100]
Nuxt99 [97–99]99 [99–99]82.5 [69–97]95.5 [92–98]99.5 [99–100]98.5 [97–99]
Angular79 [73–93]95 [85–96]73.5 [49–80]73.5 [70–95]95.5 [95–96]83.5 [75–96]

LCP (ms)

FrameworkHomeFiltered homePokémonMoveAbilityType
Marionette1,883.3 [1,865–1,893.8]1,891.4 [1,872.1–1,908.2]6,118.6 [4,410.6–6,360.8]1,903.2 [1,093.9–1,949.4]1,547.1 [1,098.2–1,994.8]1,874.8 [1,185.9–1,978.7]
Vanilla789.8 [773.1–848.3]801.1 [771.2–839.9]2,529.6 [2,294.9–4,275.3]800.6 [779–1,077.1]1,256.9 [804.8–2,014.6]801.2 [782.5–801.4]
React Router2,197.5 [2,117.2–2,236.1]2,112.1 [1,941.8–2,197.7]2,858.3 [2,842.2–2,999]1,360.5 [1,193.9–1,620.5]2,015.3 [1,989.6–2,058.5]1,904.3 [1,843.5–1,917.3]
SvelteKit1,685.9 [1,223–1,693.4]1,677.7 [1,150.6–1,687.3]2,286.3 [2,274.1–3,545.4]1,882.5 [1,148.7–1,968.1]1,682.6 [1,113.5–1,704.2]1,682.2 [1,092.1–1,749.6]
Nuxt2,003.5 [1,849.1–2,086.3]1,842.4 [1,608.1–1,983.9]3,679.9 [2,424.9–4,932.4]2,233.2 [2,144.7–2,251.5]1,754.8 [1,385.6–1,841.5]1,846.4 [1,822.8–1,996.7]
Angular1,646.3 [1,438.9–1,989.5]1,678.5 [1,426.6–1,979.2]4,949.9 [2,595.3–7,242.2]1,767.9 [1,427.6–2,261.8]1,483.4 [1,397.8–1,917.7]1,527.8 [1,406.7–1,981]

TBT (ms)

FrameworkHomeFiltered homePokémonMoveAbilityType
Marionette4.5 [1–13]0 [0–0]37 [18–72]98 [55–137]0 [0–0]29 [15.5–29.5]
Vanilla0 [0–224]0 [0–4.5]0 [0–115]100.5 [53–160.5]0 [0–0]0 [0–9]
React Router5.5 [0.5–51]0 [0–12.5]287.1 [152.5–805.5]45.5 [43–54]0 [0–6]0 [0–0]
SvelteKit0 [0–0]0 [0–0]2.5 [0–575]36.5 [5.5–65]0 [0–0]6.5 [0–34]
Nuxt45.8 [8–78]40.5 [16–63.5]145.5 [81–191]129.5 [42.5–274]44 [9–89.5]63 [32–161.5]
Angular870.4 [206–1,031.5]126.4 [90–255]456.5 [76–1,783.3]1,527.9 [112.5–2,343.2]82.5 [45–138.5]573.4 [71–921.8]

CLS

FrameworkHomeFiltered homePokémonMoveAbilityType
Marionette0.111 [0.103–0.172]0.111 [0.103–0.172]0.111 [0.103–0.111]0.111 [0.103–0.111]0.111 [0.103–0.111]0.111 [0.103–0.111]
Vanilla0.1465 [0.103–0.193]0.1465 [0.103–0.193]0.102 [0.101–0.111]0.107 [0.103–0.111]0.107 [0.103–0.111]0.107 [0.103–0.111]
React Router0 [0–0]0 [0–0]0 [0–0]0 [0–0]0 [0–0]0 [0–0]
SvelteKit0 [0–0]0 [0–0]0 [0–0]0 [0–0]0 [0–0]0 [0–0]
Nuxt0 [0–0]0 [0–0]0 [0–0]0 [0–0]0 [0–0]0 [0–0]
Angular0.109 [0.008–0.134]0.1145 [0.107–0.204]0.1075 [0.004–0.118]0.0595 [0–0.115]0.113 [0.107–0.118]0.113 [0.107–0.118]

JavaScript transfer (KiB)

FrameworkHomeFiltered homePokémonMoveAbilityType
Marionette25.56 [23.75–25.73]25.56 [23.77–25.73]25.56 [23.75–25.73]25.56 [23.75–25.73]25.56 [23.75–25.73]25.56 [23.75–25.73]
Vanilla4.48 [4.3–4.66]4.48 [4.3–4.66]4.48 [4.3–4.66]4.48 [4.3–4.66]4.48 [4.3–4.66]4.48 [4.3–4.66]
React Router115.66 [114.31–115.73]115.68 [114.36–115.73]115.66 [115.3–115.73]115.63 [114.41–115.78]115.63 [114.36–115.78]115.66 [114.36–115.78]
SvelteKit36.67 [36.48–36.85]36.66 [36.52–36.85]36.65 [36.48–36.85]36.67 [36.48–36.85]36.68 [36.52–36.85]36.68 [36.48–36.85]
Nuxt82.57 [76.01–82.69]82.52 [75.96–82.69]82.52 [75.96–82.69]82.52 [75.96–82.69]82.52 [75.96–82.69]82.52 [75.96–82.69]
Angular76.72 [49–95.75]76.72 [49–95.75]76.72 [49–97.64]76.72 [49–79.61]76.72 [49–79.61]76.72 [49–79.61]

03 / SOURCE SIZE

Maintainability

Formatter-normalized nonblank source lines

Median · line: minimum–maximum · circles: b1–b4

Formatter-normalized nonblank source lines (lines). Four builds per framework; median (minimum–maximum).
FrameworkMedian (range)b1b2b3b4
Vanilla820.0 (771.0–865.0)825.0771.0815.0865.0
SvelteKit952.5 (940.0–981.0)940.0946.0959.0981.0
Nuxt993.0 (981.0–1,031.0)997.01,031.0981.0989.0
Marionette1,012.5 (950.0–1,053.0)998.0950.01,027.01,053.0
React Router1,050.0 (1,009.0–1,167.0)1,029.01,071.01,167.01,009.0
Angular1,551.5 (1,343.0–1,890.0)1,890.01,343.01,392.01,711.0
All four independent builds. Ranges describe observations; they are not confidence intervals.

Marionette’s median was 1,012.5 nonblank lines, ranging from 950 to 1,053. It sits close to Nuxt and React Router in this cohort, with Vanilla and SvelteKit below it and Angular above. The chart’s table includes every build.

How source lines were counted

All 24 applications were formatted with Prettier 3.9.10 and Svelte plugin 4.1.1 under one file-selection policy. Counts include comments and exclude blank lines, generated output and supplied fixture files. Original source was not modified.

SvelteKit b2 is 946 lines under this policy. Counting its supplied design-token stylesheet would add 69 lines; that stylesheet is excluded consistently, including its whitespace-only variant. This is a formatted source-size comparison, not a complexity measure.

A little less guessing.

Codex built four working Marionette applications. The median generation took just over five minutes, with a token-only API equivalent of about 31 cents. That gives us a more concrete answer than a framework’s age, popularity, or an agent’s opinion about it.

For this task, Marionette took less median agent time and a lower estimated token cost than each framework peer, while delivering the same tested behavior. Vanilla was leaner still. For a framework that supposedly lost on vibes, that is a useful showing.

Marionette 5 Extra Strength bottle: for framework fatigue, stiff knees and creaky backs.

Stiff knees, maybe. But the agents didn’t seem to mind. We’re still considering launching Buy Me an Ibuprofen.

Data and method.

Download all verified per-build metrics (JSON) · Download the full six-route assessment and methodology

The downloads below contain the verified per-build assessment. The full evidence package on GitHub includes all 24 applications, their timing and usage receipts, acceptance results, all 720 Lighthouse samples, and the normalized source analysis.

Four applications per framework is a small sample. Each route metric is the median of five mobile Lighthouse samples per application; repeated measurements do not create more independent apps. Codex CLI 0.166.0 reported gpt-6.1-sol, with effort and tier overrides unset, under 4 CPU / 8 GiB container limits.

Collection dates and dependency versions differ. Hosted Marionette documentation changed between the first and second builds, though it stayed stable within each build. No MCP tool invocation was observed, but that does not establish identical exposure. Host activity, model variation and generated architecture limit causal conclusions.

The same route, browser settings and scoring weights apply to every app. Two HTTP warmups create a warm-server/cold-browser measurement: SSR loaders can warm server API caches, while fetching a client-only shell does not run its JavaScript. All SSR apps expose artwork in initial HTML. These are measurements of the generated loading architectures, not isolated framework overhead. The local API hostname mapping also differs in some retained peer measurements, with unquantified impact. Peer values remain unchanged.

Task and results sequence: Nolan Lawson · original benchmark source. The full results and application sources and methodology are available on GitHub.