THOR LAB · plan / plan
Methode + live bewijs · Inspect + Docent · 16-17 jul 2026

Onze agents
leren meten
Learning to
measure our agents

We bouwen al maanden agents, maar we meten ze niet streng. Dit plan zet één open testbank (Inspect) en één transcript-microscoop (Docent) in ons lab, met echte codevoorbeelden en een gefaseerde uitrol. We have been building agents for months but we do not measure them rigorously. This plan brings one open test harness (Inspect) and one transcript microscope (Docent) into the lab, with real code examples and a phased rollout.

Meerdere AI-agents die door één testbank en scorebord lopen
Dezelfde takenset, elke agent er doorheen, één eerlijk scorebord. Dat is wat we missen. The same task set, every agent run through it, one honest scoreboard. That is what we are missing.
Update 17 jul, 00:xx. Dit is geen plan meer, het draaide echt. Fase 0+1 staan, met vijf agents door één harde meetbank, Opus erin tegen €0, en een STAR-macro-scorer die op deze machine een echte macro draait. De live cijfers en de methode-uitleg staan hieronder, dan volgt het oorspronkelijke plan als verdieping. Update Jul 17. This is no longer a plan, it actually ran. Phase 0+1 are up: five agents through one hard bench, Opus included at €0, and a STAR macro scorer that runs a real macro on this machine. Live numbers and the method walkthrough are below; the original plan follows as depth.

Live bewijs: de methode draaideLive proof: the method ran

Stap 1 was makkelijk, en dat was het probleemRound 1 was easy, and that was the problem

Eerste suite: 8 code-taken. Alle vijf agents haalden 1.000. Mooi, maar nutteloos: een meetlat die iedereen even hoog geeft, méét niets. Dit is de sleutel-inzicht van de hele methode: een benchmark is pas een benchmark als hij de kandidaten uit elkaar trekt. First suite: 8 code tasks. All five agents scored 1.000. Nice, but useless: a yardstick that gives everyone the same height measures nothing. This is the key insight of the whole method: a benchmark only becomes a benchmark when it pulls the candidates apart.

Links vijf gelijke balken (verzadigd), rechts gespreide balken (discrimineert)
Links: verzadigd, iedereen 1.000, geen informatie. Rechts: na het verzwaren van de taken trekt de meetlat de agents uit elkaar. Dát is de meet-lus. Left: saturated, everyone at 1.000, no information. Right: after hardening the tasks, the yardstick separates the agents. That is the measurement loop.

Stap 2: verzwaar tot het discrimineertRound 2: harden until it discriminates

Ik schreef 6 randgeval-zware taken (wildcard-match, min-window, expressie-parser, geldige-haakjes, getal-naar-woorden, kolomtitel) en verifieerde eerst de asserts met eigen referentie-oplossingen (anders straf je goede code met een foute test). Zelfde harde scorer: de gegenereerde code wordt echt uitgevoerd tegen verborgen asserts. Toen trok de meetlat wél uit elkaar: I wrote 6 edge-case-heavy tasks (wildcard match, min window, expression parser, valid parentheses, integer-to-words, column title) and first verified the asserts against my own reference solutions (else you penalise good code with a broken test). Same hard scorer: the generated code is actually executed against hidden asserts. Now the yardstick did separate:

agentroute / kostenroute / costmakkelijk (8)easy (8)zwaar (6)hard (6)time-horizon
opus-4.8claude -p · €01.0001.000insensitive*
gpt-5.5OpenAI · metered1.0001.000insensitive*
gemini-3.1-proGoogle · metered1.0001.000insensitive*
glm-5.2OpenRouter · cents1.0001.000insensitive*
qwen-localThor · €01.0000.667~23 min

* De vier frontier-agents halen nog steeds alles: hun horizon is op déze suite "insensitive" (geen spreiding), en dat is een eerlijk, niet-verzonnen resultaat. Het zegt: om die vier te rangschikken moeten de taken nóg zwaarder. De lokale Qwen-coder zakt naar 0.667: hij haalt patroon-, DP- en formatteer-taken maar valt op de twee stateful-algoritmes (min_window en calc). Zijn 50%-tijdshorizon is een echte fit: ~23 minuten mensenwerk. * The four frontier agents still pass everything: their horizon on this suite is "insensitive" (no spread), an honest, non-fabricated result. It says: to rank those four, the tasks must get harder still. The local Qwen-coder drops to 0.667: it handles pattern, DP and formatting tasks but trips on the two stateful algorithms (min_window and calc). Its 50% time-horizon is a real fit: ~23 minutes of human work.

Dit is de kern: de meetlat zelf wordt geïtereerd tot hij scheidt. Verzadiging is geen succes, het is een signaal om te verzwaren. This is the core: the yardstick itself is iterated until it separates. Saturation is not success, it is a signal to harden.

De €0-truc: Opus in het leaderboard zonder gemeterde rekeningThe €0 trick: Opus in the leaderboard with no metered bill

Een premium model via een flat-fee shim in dezelfde meet-gauge
Opus loopt via een eigen Inspect-provider die onze flat claude -p (Max-plan) aanroept, niet de gemeterde console-API. SOTA in de meting, €0 marginaal. Opus runs through a custom Inspect provider that calls our flat claude -p (Max plan), not the metered console API. SOTA in the measurement, €0 marginal.

Zonder deze truc zou onze primaire denker (Opus) buiten het leaderboard vallen, want Inspect's standaard Anthropic-pad tikt de gemeterde console-rekening aan, precies wat we vermijden. De provider (providers/claudep.py, 30 regels) haalt Opus binnen tegen €0. Dat is het verschil tussen "we meten de goedkope modellen" en "we meten álles, ook onze beste". Without this trick our primary thinker (Opus) would fall outside the leaderboard, because Inspect's default Anthropic path hits the metered console bill, exactly what we avoid. The provider (providers/claudep.py, 30 lines) brings Opus in at €0. That is the difference between "we measure the cheap models" and "we measure everything, including our best".

STAR-macro-scorer: gevalideerd, en een gotcha gevangen vóór het loogSTAR macro scorer: validated, and a gotcha caught before it lied

De macro-scorer draait een gegenereerde macro echt headless en scoort op "draaide clean". Bij de eerste test vroeg STAR om een ccmpsuite-licentie die we niet hebben, en de .bat gaf tóch exit 0, dus een naïeve scorer had "OK" geloofd. Diagnose: het -power-vlag ontbrak (we hebben één ccmppowerplus-seat). Fix + succes gelezen uit de echte stdout-markers → scorer geeft nu correct True op een goede macro, False op een kapotte. Geen verzonnen getal, een gevangen gotcha. The macro scorer runs a generated macro for real headless and scores on "ran clean". On the first test STAR asked for a ccmpsuite licence we do not have, and the .bat still returned exit 0, so a naive scorer would have believed "OK". Diagnosis: the -power flag was missing (we hold one ccmppowerplus seat). Fix + success read from the real stdout markers → the scorer now correctly returns True on a good macro, False on a broken one. No fabricated number, a caught gotcha.

De methode, in 7 stappen (het herbruikbare recept)The method, in 7 steps (the reusable recipe)

Dit is het foundationele deel. Niet "we gebruikten Inspect", maar de manier van werken die je op elke agent, elk domein, kunt herhalen. This is the foundational part. Not "we used Inspect", but the way of working you can repeat on any agent, any domain.

1
Neem een open, lokale, SOTA meetbankTake an open, local, SOTA harnessInspect (UK AISI). Draait op Windows, stuurt elk model door één interface. Geen vendor-lock, geen cloud-verplichting.Inspect (UK AISI). Runs on Windows, drives every model through one interface. No vendor lock, no cloud requirement.
2
Score met een HARDE check, nooit een modelScore with a HARD check, never a modelCode wordt uitgevoerd tegen verborgen asserts; een STAR-macro draait headless. De score is een feit. Generator ≠ verifier blijft heel.Code is executed against hidden asserts; a STAR macro runs headless. The score is a fact. Generator ≠ verifier stays intact.
3
Verifieer eerst de test, dan het modelVerify the test before the modelDraai je asserts tegen een eigen referentie-oplossing. Een foute assert straft goede code, en dan meet je ruis.Run your asserts against your own reference solution. A wrong assert punishes good code, then you measure noise.
4
Haal élke agent binnen, ook je beste, tegen €0 waar kanBring in every agent, including your best, at €0 where possibleCustom provider voor Opus via flat claude -p. Anders meet je alleen de goedkope modellen en mist je je eigen referentie.Custom provider for Opus via flat claude -p. Otherwise you only measure the cheap models and miss your own reference.
5
Itereer de SUITE tot hij discrimineertIterate the SUITE until it discriminatesVerzadiging (iedereen 1.000) = verzwaar. Herhaal tot de agents uit elkaar liggen. De benchmark is een levend ding, geen vaste lijst.Saturation (everyone at 1.000) = harden. Repeat until the agents spread. The benchmark is a living thing, not a fixed list.
6
Eén getal over de tijd: METR-tijdshorizonOne number over time: METR time-horizonFit slaagkans vs mensen-tijd → het punt waar de curve 50% kruist. Herhaalbaar per modelwissel. Geen spreiding? Meld het eerlijk, verzin niets.Fit pass-rate vs human-time → where the curve crosses 50%. Repeatable per model swap. No spread? Report it honestly, fabricate nothing.
7
Maak het een regressiepoortMake it a regression gateDraai de suite bij elke modelwissel; een daling is een rode cel. Zo zien we stille achteruitgang die we nu missen.Run the suite on every model swap; a drop is a red cell. That is how we catch the silent regressions we miss today.

Waarom foundationeelWhy foundational

Dit recept geldt niet alleen voor code. Dezelfde zeven stappen meten straks CFD-macro-schrijvers, research-samenvatters, de zelf-evoluerende lus, elke plek waar we een goedkoop model een dure werkelijkheid laten nadoen. De vraag is telkens identiek: hoeveel bits verspillen we nog, en meten we het op de echte verdeling? Zodra jij dit goed snapt, bouwen we het dieper: grotere en zwaardere suites, domein-suites, en Docent als microscoop op de transcripten. This recipe is not just for code. The same seven steps will soon measure CFD macro writers, research summarisers, the self-evolving loop, every place we have a cheap model mimic an expensive reality. The question is always the same: how many bits are we still wasting, and are we measuring it on the real distribution? Once you grok this, we build it deeper: bigger and harder suites, domain suites, and Docent as a microscope on the transcripts.

1Het gat: we raden onze eigen kwaliteitThe gap: we guess our own quality

Onze Heilige Regel zegt: de slimme modellen beslissen, de goedkope genereren, en generator ≠ verifier. Maar één schakel ontbreekt: bewijs. Toen de Qwen macro-writer van 8% naar 83% slaagkans ging, was dat een handmatige meting, één keer. We hebben geen herhaalbare meetlat die zegt of een modelwissel ons beter of stiekem slechter maakt. Our Sacred Rule says: the smart models decide, the cheap ones generate, and generator ≠ verifier. But one link is missing: evidence. When the Qwen macro-writer went from 8% to 83% pass rate, that was a manual, one-off measurement. We have no repeatable yardstick that tells us whether swapping a model makes us better or quietly worse.

Je kunt niet iteren tot het klopt als je niet meet of het klopt. You cannot iterate until it is right if you do not measure whether it is right.

Dit is precies het idee uit onze compressie-notitie, maar dan op onszelf toegepast: cross-entropy / perplexity is de afstand tussen wat een model zegt en de werkelijkheid. Een testbank meet diezelfde afstand op taakniveau. We hebben de theorie al; nu de meetopstelling. This is exactly the idea from our compression note, turned on ourselves: cross-entropy / perplexity is the distance between what a model says and reality. A test harness measures that same distance at the task level. We already have the theory; now the measurement rig.

2Wat we adopteren, en waarom juist ditWhat we adopt, and why exactly this

GereedschapTool WatWhat Verdict Waarom voor onsWhy for us
Inspect
UK AISI · MIT
Open eval-framework, pip install inspect-aiOpen eval framework, pip install inspect-ai ADOPT Pure Python, draait op Windows, stuurt lokale Qwen (vLLM) + Opus + Gemini + GPT door één interface met Docker-sandbox. Onze 8%→83% wordt een echte, herhaalbare score.Pure Python, runs on Windows, drives local Qwen (vLLM) + Opus + Gemini + GPT through one interface with Docker sandboxing. Our 8%→83% becomes a real, repeatable score.
Docent
Transluce · Apache-2.0
Zelf-hostbare microscoop op agent-transcriptenSelf-hostable microscope on agent transcripts TRIAL Voor "ik heb duizenden runs, waar ging het mis". Achter de privacy-muur, of met de judge op lokale Qwen gericht.For "I have thousands of runs, where did it go wrong". Behind the privacy wall, or with its judge pointed at local Qwen.
Devin · Vivaria · Kiso · 0xPARC cloud-agent · zwaar platform · consultancy · ZK-cryptocloud agent · heavy platform · consultancy · ZK crypto SKIP Cloud-only botst met de privacy-muur, of geen fit / geen product.Cloud-only breaks the privacy wall, or no fit / no product.

Grappig detail: het bruikbaarste stuk stond niet eens op de sponsorlijst. METR (een sponsor) wijst zelf naar Inspect. We volgden het spoor tot het echte gereedschap. A funny detail: the most useful piece was not even on the sponsor list. METR (a sponsor) now points to Inspect itself. We followed the trail to the real tool.

3Waar het in onze stack pastWhere it fits in our stack

agents onder test / under test lokale Qwen Opus 4.8 Gemini 3.1 GPT-5.x · GLM INSPECT zelfde takenset Docker-sandbox harde check macro draait / test slaagt LLM-judge ander merk (Sacred Rule) leaderboard + time-horizon per agent herhaal bij elke modelwissel → regressiepoort

Inspect zit náást onze orchestrator, niet erin. Opus blijft de baas die taken uitdeelt en oordeelt; Inspect is de onafhankelijke meetbank die elk model door dezelfde taken haalt. De scorer blijft een harde check (draait de STAR-macro headless, slaagt de test) en een eventuele LLM-judge draait op een ander merk dan het geteste model. Zo blijft generator ≠ verifier overeind. Inspect sits beside our orchestrator, not inside it. Opus stays the boss that hands out and judges tasks; Inspect is the independent bench that runs every model through the same tasks. The scorer stays a hard check (does the STAR macro run headless, does the test pass) and any LLM judge runs on a different vendor than the model under test. That keeps generator ≠ verifier intact.

4Concreet: vier voorbeeldenConcrete: four examples

a · Een STAR-CCM+ macro-taak die zichzelf scoorta · A STAR-CCM+ macro task that scores itself

De agent schrijft een macro; de scorer draait 'm headless op deze host en checkt of het echt werkt. Geen mening, een feit.The agent writes a macro; the scorer runs it headless on this host and checks it actually works. Not an opinion, a fact.

tasks/starccm_macros.py
# pip install inspect-ai   (Windows, Python 3.12 — draait lokaal)
from inspect_ai import Task, task
from inspect_ai.dataset import json_dataset
from inspect_ai.solver import system_message, generate
from inspect_ai.scorer import scorer, Score, Target, accuracy, CORRECT, INCORRECT
import subprocess, re, pathlib

def run_macro_headless(java: str) -> bool:
    # schrijf .java, draai STAR-CCM+ 21.02 -batch, check exit + verwacht resultaat
    p = pathlib.Path("_eval/gen.java"); p.write_text(java, encoding="utf-8")
    r = subprocess.run(["starccm+", "-batch", str(p), "-np", "4"],
                       capture_output=True, text=True, timeout=1800)
    return r.returncode == 0 and "Command finished" in r.stdout

@scorer(metrics=[accuracy()])
def macro_runs():
    async def score(state, target: Target) -> Score:
        java = re.search(r"```java(.*?)```", state.output.completion, re.S)
        ok = run_macro_headless(java.group(1)) if java else False
        return Score(value=CORRECT if ok else INCORRECT)
    return score

@task
def starccm_macros():
    return Task(
        dataset=json_dataset("starccm_tasks.jsonl"),   # onze eigen taakset
        solver=[system_message("Je schrijft STAR-CCM+ 21.02 Java-macros. Gebruik de geverifieerde API."),
                generate()],
        scorer=macro_runs(),
    )

b · Elke agent door dezelfde taken → leaderboardb · Every agent through the same tasks → leaderboard

run_leaderboard.py
from inspect_ai import eval

eval("tasks/starccm_macros.py", model=[
    "openai/qwen-coder-awq",        # lokale Qwen op Thor (OpenAI-compat, base_url=THOR-LAN:8000)
    "anthropic/claude-opus-4-8",
    "google/gemini-3.1-pro-preview",
    "openai/gpt-5.5",
])
# → inspect view  opent een web-UI met de score per model, per taak, klikbaar.
Wat dit oplevert: één tabel, appels-met-appels: "op onze 30 echte STAR-taken haalt lokale Qwen 83%, Opus 91%, GPT 88%". Nu is modelkeuze een meting, geen gevoel. En het is Sacred-Rule-zuiver: de score is een harde check, geen model dat zichzelf beoordeelt.What this gives: one apples-to-apples table: "on our 30 real STAR tasks, local Qwen scores 83%, Opus 91%, GPT 88%". Now model choice is a measurement, not a feeling. And it is Sacred-Rule-clean: the score is a hard check, not a model grading itself.

c · METR-tijdshorizon: worden we beter over de tijd?c · METR time-horizon: are we improving over time?

Meet één keer hoe lang een mens over elke taak doet. Fit dan slaagkans vs log(mensentijd). Het punt waar de curve 50% kruist = de "tijdshorizon" van de agent. Eén getal, herhaalbaar bij elke modelwissel.Measure once how long a human takes per task. Then fit success-rate vs log(human-time). The point where the curve crosses 50% = the agent's "time horizon". One number, repeatable on every model swap.

time_horizon.py
import numpy as np
from scipy.optimize import curve_fit

def logistic(log_t, k, t50): return 1 / (1 + np.exp(k*(log_t - t50)))

# human_minutes + pass(0/1) per taak, uit de Inspect-run
popt, _ = curve_fit(logistic, np.log(human_minutes), passed, p0=[1.0, 2.0])
horizon_min = np.exp(popt[1])
print(f"50%-tijdshorizon: {horizon_min:.0f} min mensen-werk")

d · Docent: een faalpatroon uit duizenden runs vissend · Docent: fishing a failure pattern out of thousands of runs

Voed onze multi-agent transcripten in Docent, schrijf een "rubric" in gewone taal, en krijg een gekwantificeerd percentage + klikbare voorbeelden. Direct bewijs voor de Sacred Rule.Feed our multi-agent transcripts into Docent, write a plain-language "rubric", and get a quantified rate + clickable examples. Direct evidence for the Sacred Rule.

docent_rubric.txt
# zelf-gehost (docker-compose + Postgres), judge → lokale Qwen
rubric: "Markeer elke run waarin de generator stil naar een
        zwakker model downgradede, of de verifier een
        geplante bug miste."
# → Docent scoort alle runs, geeft % + de exacte stappen waar het misging.

5Uitrol in fasenPhased rollout

0
Installeren (½ dag)Install (½ day)pip install inspect-ai, lokale Qwen als OpenAI-compat provider (Thor :8000), één speeltaak groen krijgen.pip install inspect-ai, wire local Qwen as an OpenAI-compat provider (Thor :8000), get one toy task green.
1
Drie echte scorers (1 dag)Three real scorers (1 day)STAR-macro-draait, code-test-slaagt, research-samenvatting-dekt-bronnen. Onze bestaande signalen worden formele Inspect-scorers.STAR-macro-runs, code-test-passes, research-summary-covers-sources. Our existing signals become formal Inspect scorers.
2
30-taken suite + leaderboard (2-3 dagen)30-task suite + leaderboard (2-3 days)Genoeg taken voor een stabiel getal. Draai lokale Qwen / Opus / Gemini / GPT / GLM er doorheen.Enough tasks for a stable number. Run local Qwen / Opus / Gemini / GPT / GLM through it.
3
Tijdshorizon + regressiepoortTime-horizon + regression gateEén getal per agent, opnieuw gedraaid bij elke modelwissel. Zakt de score na een upgrade? Dan zien we het meteen.One number per agent, re-run on every model swap. Score drops after an upgrade? We see it instantly.
4
Docent-trial (privacy-gated)Docent trial (privacy-gated)Eerst checken of de judge op lokale Qwen kan; dan alleen geschoonde/externe runs erin, faalpatronen minen.First confirm the judge can run on local Qwen; then feed only scrubbed/external runs, mine failure patterns.

6Hoe het ons sterker maaktHow it strengthens us

Een stijgende capaciteit-over-tijd curve met een klein leaderboard
Van "het voelt beter" naar een curve die stijgt of daalt. Bewijs, niet gevoel.From "it feels better" to a curve that rises or falls. Evidence, not vibes.

1 · Regressiepoort op elke modelwissel1 · Regression gate on every model swap

Nieuw Opus, nieuwe Qwen, nieuwe GLM: draai de suite, vergelijk het getal. Stille achteruitgang (waar we nu blind voor zijn) wordt een rode cel.New Opus, new Qwen, new GLM: run the suite, compare the number. Silent regressions (which we are blind to today) become a red cell.

2 · Modelkeuze wordt een meting2 · Model choice becomes a measurement

"Welk model voor STAR-macro's" wordt beantwoord door de score op echte STAR-macro's, niet door een onderbuikgevoel of een marketingclaim."Which model for STAR macros" gets answered by the score on real STAR macros, not a gut feeling or a marketing claim.

3 · Hard bewijs voor de Heilige Regel3 · Hard evidence for the Sacred Rule

Docent kwantificeert precies hoe vaak een dom model een slim besluit voedde, of een verifier een bug miste. De regel wordt meetbaar in plaats van een principe.Docent quantifies exactly how often a dumb model fed a smart decision, or a verifier missed a bug. The rule becomes measurable instead of a principle.

4 · Sluit de compressie-cirkel4 · Closes the compression loop

De compressie-notitie zei: intelligentie = de KL-kloof verkleinen. Een Inspect-scorer die perplexity of taak-slaagkans meet, is letterlijk die kloof, op onze eigen taken. Theorie en meetbank sluiten op elkaar aan.The compression note said: intelligence = shrinking the KL gap. An Inspect scorer measuring perplexity or task pass-rate is literally that gap, on our own tasks. Theory and bench line up.

We stoppen met raden of we beter worden, en gaan het meten. Dat is de Iterate-Until-Right-lus met eindelijk een echte meter. We stop guessing whether we are improving and start measuring it. That is the Iterate-Until-Right loop with, at last, a real gauge.

7Bewakingen (zodat het niet tegen ons werkt)Guards (so it does not backfire)

Een vergrootglas dat één afwijkende stap in een agent-transcript markeert
Docent = het vergrootglas op de faalstap. Krachtig, maar het leest transcripten, dus: privacy-muur eerst.Docent = the magnifier on the failure step. Powerful, but it reads transcripts, so: privacy wall first.

Volgende stapNext step

Zeg het woord en ik zet Fase 0+1 op: Inspect geïnstalleerd, lokale Qwen als provider, en de eerste STAR-macro-scorer groen op een handvol echte taken. Dan heb je binnen een dag het eerste mini-leaderboard om naar te kijken. Say the word and I stand up Phase 0+1: Inspect installed, local Qwen as a provider, and the first STAR-macro scorer green on a handful of real tasks. You would have a first mini-leaderboard to look at within a day.