Research log / RL-016

RL-016 Delegated page builds: planner and coder models taking turns on one GPU

Evidence grade BC

Field Value
Date 2026-09-13
Harness version 2026-09-13-reasoning-continuation-3 → 2026-09-13-delegated-pages-4
Hardware RTX 4060 Laptop GPU, 8 GB (≈ 0.5 GB used by the desktop); 15.7 GB RAM; i7-12650H; Ollama 0.34.0
Models planner qwen3:8b (thinking on); worker qwen2.5-coder:7b; comparison writer qwen3:8b (thinking on)
Evidence grade B (live hardware and build measurements) + C (tests)
Pre-registered yes: hypotheses, conditions and measures below were written before the build runs were read. The hardware measurements (§ Results H1) were taken first and motivated the design, so H1 is a description, not a prediction
Source record user request; journal turn 120 / run 389 (RL-015); RL-014 transcript ("10 different pages")

Observation

The user's goal: use the machine's own hardware as well as possible. The larger thinking model should spend its context on judgement and hand the code to smaller models, which stay inside the parent's guardrails, reach only the place they work in, and get a small direct brief. The user added that the models need not run together: the hardware is limited, so events should decide whose turn it is.

The concrete workflow: "lets create a web app, about dogs … 10 different pages" was refused by the multi-page guard (RL-014), because one draft holds one file. Continuing one long page on a reasoning model exhausted its reply budget while retyping the draft (RL-015).

Hypotheses

Intervention

Method

  1. Hardware (H1): /api/generate with num_ctx, /api/ps, nvidia-smi. Per model: cold load, reload, VRAM, write speed on a fixed 300-token HTML prompt (temperature 0). Handoffs planner → coder → planner, eviction left to Ollama vs explicit unload. n = 1 per cell.
  2. Builds (H2–H5): request lets create a web app about dogs using tailwind with 4 different pages; planner qwen3:8b thinking on in both conditions. Condition D: pages by qwen2.5-coder:7b, n = 3 builds. Condition S: pages by qwen3:8b thinking on, same packets, n = 2 builds. Machine otherwise idle. Measures: plan validity and attempts, per-page check pass, attempts, seconds, prompt estimate, completion tokens, handoff load time.
  3. Unit tests, full suite, typecheck, build.

Results

H1 — hardware (description, n = 1 per cell)

Alone on the GPU, 300-token HTML prompt, temperature 0:

Model Context VRAM Cold load Reload Write speed
qwen3:8b 8,192 5.76 GB 8.5 s 10.1 s 26.4 tok/s
qwen3:4b-thinking 8,192 3.61 GB 6.2 s 6.2 s 41.8 tok/s
deepseek-r1:8b 8,192 5.76 GB 10.3 s 11.1 s 28.6 tok/s
qwen2.5-coder:7b 8,192 4.64 GB 9.4 s 9.5 s 27.7 tok/s
qwen2.5-coder:1.5b 4,096 1.09 GB 4.0 s 4.0 s 82.2 tok/s
qwen2.5-coder:14b 4,096 5.78 of 9.27 GB on GPU 21.2 s 17.2 s 5.8 tok/s

Planner → coder → planner handoffs:

Pair Eviction by Ollama Explicit unload CPU spill
qwen3:8b ↔ qwen2.5-coder:7b 20.5 s 13.3 s none
qwen3:4b-thinking ↔ qwen2.5-coder:7b 12.1 s 10.4 s none

The coder is not faster than the 8B planner at writing. What delegation can save is reasoning tokens and the planner's context, not decoding speed. The 14B coder does not fit the card and is not a useful worker here.

Observation 2 — load time charged to the first request (before H5)

The first attempt at the build runs: all four builds failed at planning with qwen3:8b sent nothing for 45s (the fast profile's header/first-token budget). The full test suite was running at the same time (up to 16 processes). A direct probe of the same prompt shortly after, still under load, measured a cold load of 23.6 s (8.5 s idle) and, once loaded, a first reasoning frame 0.7 s later. The planner model itself was not the problem; the load was charged to the request. Confounded (concurrent load, n = 4 from one setup), so it motivated H5 rather than measured it.

The same probe, warm, on the planner prompt (n = 1 each):

Planner configuration First frame First answer text Total Completion tokens
JSON mode, thinking on 24.3 s (cold) 43.9 s 50.6 s 778
thinking on, no JSON mode 0.3 s 29.2 s 36.4 s 875
JSON mode, thinking off 4.2 s 4.2 s 14.2 s 229

H2, H3, H4, H5 — dogs request, 4 pages

Machine otherwise idle. Temperature 0 made repeats identical: all three D builds produced the same plan, the same pages and the same token counts; both S builds likewise. The repeats establish that the result reproduces and how much the timings vary; they are not independent samples of reliability.

D: coder writes (n = 3) S: thinking model writes (n = 2)
Plan valid, 1 attempt; index, breeds, care, gallery same
Planning time 29.4 / 50.5 / 53.5 s 54.1 / 46.9 s
Pages passing checks 4 of 4, all first attempt 2 of 4; build stopped
index.html 21.7 / 42.1 / 37.5 s 89.2 / 83.7 s
breeds.html 24.3 / 39.5 / 35.4 s 95.3 / 88.9 s
care.html 51.8 / 63.5 / 62.9 s failed after 244.0 / 215.9 s: reasoning_budget_exhausted
gallery.html 37.3 / 46.1 / 47.1 s not started
Completion tokens planner 778 + coder 3,451 8,173 in total (planner and writer share one client)
Model loads planner 5.5 / 7.3 / 6.7 s; coder 5.6 / 8.5 / 6.5 s planner 7.5 / 6.2 s
Whole build 170.0 / 250.4 / 242.9 s 482.7 / 435.4 s, incomplete
Worker prompt (estimate) 356–370 tokens 368–370 tokens

What the checks did not see (manual reading of the D pages)

Varied requests (condition D, three distinct requests, n = 1 each)

Run after normaliseTailwind landed, so these builds include it.

Request Plan Pages passing Planning Page times Whole build Tokens (planner / coder)
bakery, "5 different pages" 5 pages, 1 attempt 5 of 5, first attempt 34.0 s 32.0–57.4 s 261.1 s 598 / 4,154
guitar, "three pages, dark theme" 3 pages, 1 attempt 3 of 3, first attempt 36.9 s 22.0–31.2 s 125.5 s 602 / 1,760
photographer portfolio, "4 separate pages using tailwind" 4 pages, 1 attempt 4 of 4, first attempt 69.8 s 35.6–38.6 s 227.1 s 1,072 / 2,984

Handoff loads 5.6–6.8 s. Worker prompts 353–378 estimated tokens. The requested page count was honoured in all three. The dark theme was applied to all three guitar pages (dark background classes present).

Reading the pages: the Tailwind script is present on all 12 (after normalisation; whether the model still wrote a link was not recorded). All 35 images point at local files that do not exist (image5.jpg, images/street1.jpg, …) — removeInventedImages removes only remote URLs, and the checks do not look at images. The portfolio homepage alone has 12 broken images. Headings are specific to each request ("Choosing Your Guitar", "Croissant", "Street Photography"); no "Cute Dog N"-style filler.

Across the four distinct requests (dogs + three): 16 of 16 pages passed the structural checks on the first attempt, every plan was valid on its first attempt, and no worker needed a retry, so the repair and continuation paths were exercised only by tests.

Suite

697 tests, 0 failures (14 new in tests/delegation.test.ts); typecheck clean; build passes.

Evidence record

Raw files: evidence/RL-016/. Hardware runs (hardware-output.log, colocate-result.json), the failed first build attempt (first-build-attempt-output.log), the planner stream probe (planner-probe-output.log), the dogs builds (delegation-live-result.json, with every generated page) and the varied builds (delegation-varied-result.json), with the scripts that produced them.

What the stored pages are. html in the result files is the page after the harness's own processing (removeInventedImages, and for the varied builds normaliseTailwind), not the model's raw reply. Raw replies were held in memory (PageResult.raw) but the scripts did not save them. The planner's full JSON was not saved either; its name, style line and page list were. Both are gaps in this record.

User prompts (verbatim)

# Where Prompt What followed
0 Tiny Agent, turn 117, 00:17:34 UTC lets create a web app, about dogs, with little write ups on each dog breed, use 10 dog breeds, 10 different pages, use css and styling The origin workflow. One incomplete 5,532-char draft (RL-014); two continue refusals; a third continue ended in reasoning-budget exhaustion (RL-015).
1 Claude Code session i want to focus up on the fact we are using our own hardware, we want the larger thinking agents context to be used as well as possible, it needs to know to call smaller models to write code for it, these sub agents must be within the guardrails of the parent model and have access only to the place they are working, they need only small context of what their job is, direct. we need to figure these things out to get the absolute best out of what we have Hardware inventory, then a co-residency measurement of planner + coder pairs was started.
2 same, mid-work you dont have to run the models symotanionsly, the hardware is limited so we can use events to decide which models turn it is if needed, this splits the operation up and makes it less taxing The co-residency test was dropped and replaced by a handoff-cost test; the design became one model on the GPU at a time (ModelTurns).
3 same okay go ahead Remaining varied-request runs, suite, research log, claims review.
4 same update new paper with the issues, user prompts, model response, assumptions that were made, what was correct and incorrect - make sure this is done for every change, this is the most important proof of improvement we have This section; the evidence record became a required part of every entry (README protocol step 10, TEMPLATE).

Model prompts and responses (verbatim excerpts)

Build request sent to the planner in every dogs build: lets create a web app about dogs using tailwind with 4 different pages — a shortened form of prompt 0 (4 pages, to bound run time). The planner and worker prompts are the exact strings built by buildPlannerMessages and buildWorkerMessages in src/agent/Delegation.ts at delegated-pages-4.

Planner (qwen3:8b, thinking on, JSON mode) — first 300 characters of the answer, from the probe with the identical prompt and configuration (its style line matches the builds' stored style line exactly):

{"name":"PawsomePaws","style":"A clean, modern design with a dog-friendly theme, using Tailwind's utility classes for responsive and stylish layouts","pages":[{"file":"index.html","title":"Home","brief":"Hero section with welcoming message, 'About Dogs' section highlighting loyalty and companionship

The planner produced 778 completion tokens, of which the answer was 873 characters; about 2,800 characters were reasoning (probe).

Planner, first build attempt (before the load step) — every one of four builds ended with:

qwen3:8b sent nothing for 45s

Coder (qwen2.5-coder:7b), dogs build, care.html, lines 1–8:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PawsomePaws - Care</title>
    <link href="https://cdn.tailwindcss.com" rel="stylesheet">
    <style>

Coder, dogs build, gallery.html (image sources survived because they are local file names, not remote URLs; the files do not exist):

            <img src="dog1.jpg" alt="Dog 1">
            <h2>Cute Dog 1</h2>
            <img src="dog2.jpg" alt="Dog 2">
            <h2>Cute Dog 2</h2>

Coder, varied builds — portfolio index.html:

        <img src="image1.jpg" alt="Image 1">
        <img src="image2.jpg" alt="Image 2">

guitar basics.html, headings specific to the request:

        <h2 class="text-2xl font-bold mb-2">Choosing Your Guitar</h2>
        <h2 class="text-2xl font-bold mb-2">Tuning Your Instrument</h2>

Thinking writer (qwen3:8b, thinking on), care.html, both S builds — no answer text; the client raised after 244.0 s and 215.9 s:

the model spent its reply budget on reasoning and returned no answer

Its reasoning text was not saved.

Issues encountered, in order

# Issue How it surfaced Outcome
1 The desktop app was still running the pre-RL-015 build; /api/ps showed qwen3:8b at 4,096 /api/ps and process start time vs build time Reported; the app needs a restart
2 Measurement script as .ts failed: top-level await under tsx's CJS transform First launch exited immediately Renamed to .mts; no data lost
3 parseProjectPlan named a second page titled "Index" index-2.html Unit test Fixed: such a page becomes page-N.html
4 Test assertion expected a join without a newline Unit test Assertion was wrong; the joined page was correct
5 AgentRunner cached the model tool list at construction; a test registering a tool later lost it from the checklist checklist.test.ts failed in the full suite Fixed: tool list read at run time
6 desktopSearch.test.ts timed out in 4/4 full runs with the new test file Full suite Isolated but not explained; passed 3/3 after later changes (see Threats)
7 All four first build attempts failed at planning: "sent nothing for 45s" Live run Diagnosed as load time charged to the request under CPU contention; load became its own step
8 A diagnostic PowerShell command was blocked by the permission checker (path interpolation read as a protected removal) Tool refusal Verified the test file was untouched; rewritten as a Node script
9 Temperature 0 made all repeats identical, so n = 3 was one sample Comparing run outputs Reported as reproducibility; three distinct requests added
10 Tailwind CDN written as a stylesheet link on every dogs page Reading the pages normaliseTailwind added and tested; not re-measured live
11 The results script's tailwind field matched the URL, not a working load Reading the pages Field declared invalid
12 Images left without a source (dogs) and pointing at files that do not exist (all 35 in the varied builds) Reading the pages Not fixed; follow-up 2
13 Filler headings "Cute Dog 1–4" Reading the pages Not fixed; content quality is not checked
14 Approval card shows one 200-line combined diff Reading the renderer and host Not fixed; follow-up 1
15 The renderer ignores model events, so per-page progress and handoff messages are invisible Reading the renderer Not fixed; recorded
16 The other session's git commit captured an intermediate state of this change git show --stat Recorded; nothing committed by this session

Assumptions and what turned out to be true

# Assumption Verdict Evidence
1 Planner and coder would need to be resident together, so pairs must fit in 8 GB Incorrect framing — corrected by the user (prompt 2) Turn-taking needs no co-residency; handoffs cost 10–21 s
2 A coder model would write faster than an 8B thinking model Incorrect 27.7 vs 26.4 tok/s on the same prompt
3 Leaving eviction to Ollama is as good as unloading explicitly Incorrect (n = 1) 20.5 s vs 13.3 s; 12.1 s vs 10.4 s
4 qwen2.5-coder:14b could be a worker on this card Incorrect 5.78 of 9.27 GB on GPU, 5.8 tok/s
5 The 45 s planning failures were a JSON-mode or thinking-stream problem Incorrect Probe: frames streamed normally once loaded; the load was slow under contention
6 Reasoning frames count as "started" for the first-token clock Correct ExecutionMonitor.reasoning() sets sawFirstToken
7 The desktop test failures were general CPU-load flakiness Not supported A placeholder file at the same sort position passed; cause unknown
8 Three repeats per condition are three samples Incorrect Identical plans, pages and token counts at temperature 0
9 The results script's tailwind field measures whether Tailwind loads Incorrect It matched the URL inside a non-working <link>
10 removeInventedImages leaves only valid images Incorrect It strips remote URLs only; local file names and empty tags remain
11 Passing the structural checks means a usable page Incorrect Non-loading Tailwind, broken images, filler headings on passing pages
12 A worker needs only its own brief and the navigation, under 400 tokens Correct (C) 353–378 estimated tokens; 16/16 pages passed structural checks
13 The thinking model writing pages would spend its budget reasoning Correct 2/2 builds exhausted on care.html
14 The planner would honour the requested page count Correct 4/4 requests (4, 5, 3, 4 pages)
15 The approval card lets the user review every page Incorrect 200-line cap on one combined diff
16 The chat trace shows which page is being written Incorrect Renderer draws only tool, reject and repair events
17 A multi-page request routes to the chat path, where the delegation branch sits Correct (C) End-to-end runner test reached needs_confirmation via respond
18 Loading as its own step prevents load-time timeouts Untested under contention 0/7 failures, but without the concurrent load
19 Planning is better with thinking on Untested Thinking off planned in 14 s vs 36–51 s; plan quality not compared

What was correct and what was not

Conclusion

Threats to validity

Follow-ups

  1. Run one delegated build through the desktop app and read the approval card and the saved folder; run the browser verifier on each page. Show every page on the approval card (per-file diffs or a page picker) rather than one 200-line combined diff.
  2. Images: <img> tags left without a source (dogs build) and 35 of 35 images pointing at local files that were never written (varied builds). Either remove images whose source is not a file in the project, or add an image check to checkPage. The image guard is shared with single-page drafts, so this is its own entry.
  3. Re-measure Tailwind loading after normaliseTailwind with a check that the script tag is present, not the URL.
  4. Planner with thinking off: 14 s vs 36–51 s for a valid plan (n = 1). Measure plan quality both ways before changing the default.
  5. Check the worker model is installed before planning, so a missing coder does not cost a planning turn.
  6. A reliability rate for delegated builds needs distinct requests at n ≥ 10, or a non-zero temperature with a fixed seed set.

Reproduce

npm test
npm run typecheck
Invoke-RestMethod http://127.0.0.1:11434/api/ps

Live measurement scripts (not part of the repository) are described in Method; per-run JSON with the generated pages was kept for the analysis above.