Skip to content

Commit 5c864b5

Browse files
committed
content(blog): twenty-eight models across six surfaces, and bump to v1.2.3
Reports the 840-run protocol behind Agent mode's local model roster and the five server-side error paths that were failing runs the models could have finished, including the refusal that stated one side of a comparison.
1 parent 0b83dad commit 5c864b5

2 files changed

Lines changed: 184 additions & 1 deletion

File tree

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
---
2+
title: 'Twenty-eight models, six surfaces, eight hundred and forty runs'
3+
status: published
4+
author:
5+
name: LibreDB
6+
picture: ''
7+
slug: twenty-eight-models-six-surfaces-840-runs
8+
description: What it took to make a local model drive a database agent, and why the model was almost never the problem. Nearly every time a model failed, the ledger showed our own server holding the sentence that would have unblocked it.
9+
coverImage: ''
10+
tags:
11+
- value: engineering
12+
label: Engineering
13+
- value: databases
14+
label: Databases
15+
publishedAt: 2026-09-08T20:30:00.000Z
16+
---
17+
18+
LibreDB Studio's Agent mode does something ordinary to describe and hard to do: it reads a database and reports what it found, citing the readings it took.
19+
It can do that with a hosted model.
20+
The question we set out to answer was whether it could do it with a model running on a laptop, from Ollama, with no API key and no data leaving the machine.
21+
22+
The answer is yes, for twenty-eight models today.
23+
Getting there took eight hundred and forty runs and, more to the point, it took admitting something uncomfortable: nearly every time a model failed, the ledger showed our own server holding the sentence that would have unblocked it.
24+
25+
This is that story, told from the measurements.
26+
27+
## The protocol, and why it is this strict
28+
29+
A model is not "supported" because it worked once.
30+
Agent mode has six surfaces, investigate, optimize, assess, operate, analyze and plan, and each one asks for something different.
31+
A model that lists tables beautifully may be unable to compare two query plans; one that compares plans may never file a report.
32+
33+
So a **cell** is one model on one surface, and a cell is only closed when the model passes **five consecutive runs** on it.
34+
Six cells, thirty runs, and nothing below 30/30 ships.
35+
Twenty-eight models at 30/30 is **840 runs, and all 840 passed.**
36+
37+
Three rules keep that number honest:
38+
39+
**A lock is never taken back.**
40+
Once a cell reads 5/5 it is never re-measured or re-tuned.
41+
If a later change would alter what that cell was measured under, the cell is read again from scratch rather than inherited.
42+
43+
**Every measured model carries an entry recording the settings its numbers were obtained under**, even when those settings are the defaults.
44+
A shared value is what has twice cost this project a cell it had already won: a sampling temperature pinned globally won five cells and lost one; a reordering of the workflow rules won nothing and lost one.
45+
The entry means a later change to the defaults cannot silently invalidate a measurement.
46+
47+
**Four out of five is variance, not a signature.**
48+
A cell at 4/5 is re-rolled at the same settings, up to three times, before anything is changed.
49+
Reaching for a knob at 4/5 is how you convince yourself a setting worked when the dice simply landed differently.
50+
51+
## The bug we found eight times
52+
53+
Early on the losses looked like model failures.
54+
A run would end having produced nothing, the verdict would read `no-report`, and the obvious conclusion was that the model was not good enough.
55+
56+
Then we read the wire recording of a `qwen3:8b` run.
57+
58+
The model called `compose_report`.
59+
The server refused it with:
60+
61+
> The arguments did not match the shape this tool declares.
62+
63+
The model called it again, unchanged.
64+
Same sentence.
65+
It did that for **thirty-seven turns** until the run ran out of budget and ended having reported nothing.
66+
67+
The sentence is true.
68+
It is also unusable: it names no field, so there is nothing in it to act on.
69+
The validation layer knew exactly which path had failed and what was expected there, and was throwing that away before speaking.
70+
71+
Naming the field fixed that run.
72+
What it did not fix was the shape of the mistake, which we then found again, and again:
73+
74+
**A tool refused a recommendation thirty-two times in one run** over one field whose permitted values were `index` and `rewrite`.
75+
The refusal said "invalid value", never the two words that would have worked.
76+
The permitted set was in the schema, in hand, at the moment of refusal.
77+
78+
**A run that stopped without reading anything** was ended in silence.
79+
The server held a sentence written for exactly that ending, *"Read it yourself. Call inspect_schema for the tables and their columns, and inspect_plan for how a statement will run"*, and sent it only to models whose profile asked for it.
80+
One profile of twenty-eight did.
81+
A model nobody has measured has no profile at all, so the model most in need of the sentence was the one guaranteed not to receive it.
82+
Across one sweep: 300 runs ended `model-stopped` with `no-report`, **half of them had called no tool at all, and 94 of those were ended without the drive saying anything.**
83+
84+
**A reasoning model wrote its thinking into the tool-call argument field**, with valid JSON after it.
85+
The endpoint could not parse the whole string, reported exactly that, and handed back the model's own text.
86+
Studio threw that away and killed the run, seventeen seconds into a 630-second budget, with four tools already called.
87+
The retry it did have was written for a broken connection: it re-sent byte-identical bytes, which asks the model to make the same mistake again, and it does.
88+
89+
**The largest one was the quietest.**
90+
Across twelve hours of sweeps, `compose_report` was declined 1,530 times, and one issue was **1,110 of them**:
91+
92+
> the fields that did not match, claims: expected array
93+
94+
True, and still not enough.
95+
A model that serialized `claims` as a JSON *string* reads that and agrees with it: what it sent is an array, as far as it can tell.
96+
Nothing in the sentence contradicts it, so it sends the same bytes again.
97+
We were stating one side of a comparison and calling it a message.
98+
99+
The fix was to name what arrived as well as what was expected:
100+
101+
> claims: expected array, received string
102+
103+
After that change, that error stopped appearing entirely; the failures moved one level deeper into the schema, where the message was already specific enough to act on.
104+
105+
## What the fixes were worth
106+
107+
The clearest single measurement: `gpt-oss:20b` on the assessment surface.
108+
Four separate sweeps and two different per-model settings had it at 1/5, 1/5, 2/5, 2/5.
109+
The cell would not open.
110+
The cause was not the model, it was the parse failure above, killing runs that had already read what they needed.
111+
112+
With the fix, and **with no per-model setting at all**, the same cell read **5/5 on its first reading.**
113+
114+
Then there is `deepseek-r1:8b`, which our own notes had written off in plain language: *a thinking model spends the turn thinking and never calls a tool.*
115+
Re-measured on the corrected build, it locked **five of six surfaces, four of them on the first attempt.**
116+
117+
The same thing shows up in the roster we shipped.
118+
`cogito:32b` spent weeks recorded as unsupported over one cell it could not close, and the cell turned out to be the server's.
119+
120+
That is the finding underneath all of this.
121+
We had been grading models against a harness that was withholding half of what it knew.
122+
123+
## What a model still has to bring
124+
125+
None of this means every model can do the job, and the measurements are just as clear about that.
126+
127+
Some cannot participate at all.
128+
Ollama reports no tool capability for `gemma3`, and a planning run drives to `succeeded` having called nothing: the model a reader would most expect on this list is one that cannot drive an agent surface.
129+
130+
Some produce the intent without the protocol.
131+
`mistral:7b` receives the instruction, understands it, restates it, *"I will first use the `inspect_schema` function... then `inspect_plan`... finally `compose_report`"*, and then writes the call as prose inside a code fence instead of making it.
132+
Across twenty-five agent runs it called **zero** tools.
133+
That is a wall no sentence reaches.
134+
135+
And some are held by the clock rather than by ability.
136+
One model's optimization runs began calling tools the moment its per-turn ceiling was raised, going from zero tool calls to three, four, five, and then spent the entire run budget without finishing.
137+
That is an honest "not supported at this standard", not a knob to keep turning.
138+
139+
## The hardware, stated plainly
140+
141+
Memory is the binding constraint on a laptop, and it is not the number on the download page.
142+
**RAM costs roughly twice the size on disk** with a default context window.
143+
144+
We learned that the expensive way.
145+
A 42 GB model loaded into 86 GB on a 64 GB machine: free memory reached 5%, 782,000 pages went to swap, and the machine was seconds from locking up while its owner was using it.
146+
The proven ceiling on that hardware is a 27 GB model.
147+
Capping the context (`OLLAMA_CONTEXT_LENGTH=32768`) is what keeps the larger ones inside it.
148+
149+
For calibration, from the passing runs:
150+
151+
| Position | Model | Median run |
152+
| --- | --- | --- |
153+
| Fastest measured | `qwen2.5:7b` | **6s** |
154+
| Fastest hosted | `gemini-3.5-flash-lite` | 10s |
155+
| Slowest | `muse-glimmer:latest` | 115s |
156+
157+
A nineteen-fold spread across models that all score identically.
158+
Every one of them is 30/30; what separates them is how long you wait.
159+
160+
## Twenty-eight, from eight vendors
161+
162+
The roster spans Google, IBM, NVIDIA, Alibaba, Mistral AI, Deep Cogito and others, from 2.5 GB to 27 GB on disk.
163+
The smallest model on the list, `qwen3:4b` at 2.5 GB, holds all six surfaces.
164+
So does a 27 GB one.
165+
Size predicts speed here far better than it predicts capability.
166+
167+
What the roster does not span is our opinion.
168+
A model is on it because thirty runs passed, and off it because they did not.
169+
170+
## The part worth taking away
171+
172+
We began by asking which models were good enough.
173+
We ended up rewriting our own server five times, and the pattern was the same on every one of them: **the answer was in the server's hand and it refused without saying it.**
174+
175+
A refusal that names no remedy is not a safety feature.
176+
It is a wall the model cannot climb, and the run it ends was going to fail anyway, so saying the missing half costs nothing and is sometimes the entire difference between 1/5 and 5/5.
177+
178+
If you are building something a model drives, that is the question worth asking of every error path you own: *does this sentence tell the model what to do differently?*
179+
If it does not, you are not measuring the model.
180+
You are measuring your own silence.
181+
182+
Every figure here comes from the run ledger.
183+
The per-model settings, the protocol, and what each model was measured under are in the repository under `docs/llms/`.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "libredb-website",
3-
"version": "1.2.2",
3+
"version": "1.2.3",
44
"private": true,
55
"type": "module",
66
"description": "libredb.org — marketing website for LibreDB Studio (Astro static + Outstatic CMS)",

0 commit comments

Comments
 (0)