Zum Inhalt springen
lernapps.net
Menü

Diese Seite gibt es nicht auf Deutsch.

Prompt engineering › How we evaluate the guidance

How we evaluate the guidance

Our guidance tells a creator's AI assistant how to build a learning app together with a person: the skills, the plan template, llms.txt. We test it the way it is used. A simulated teacher talks to a real assistant, from the first message to the question whether the app may go online. Then we score what the assistant built and how it got there.

Why a whole conversation

What the guidance asks for happens across many messages:

None of this shows in one answer to one prompt. Did the assistant wait for the yes? Did it create the app only after the plan was confirmed? Only a whole conversation tells. So each eval is one scenario, played from start to end, in a fresh container, with the assistant's real tools.

Why not test single prompts? They are cheaper and faster, but they test the model's answer, not the process. A guidance that reads well can still be skipped in turn five.

What happens in one trial

One trial Inside one Harbor trial, the simulated creator decides each message and sends only its reply, through the creator's command and acpx over ACP, to the assistant under test. The assistant answers and works in the app folder. Harbor keeps three things: the creator's decisions, the ACP session and the app folder. ONE HARBOR TRIAL · ONE FRESH CONTAINER Simulated creator Claude Code playing a teacher The creator's command records the decision, sends the reply Assistant under test Claude Code or OpenCode, one model decides reply, over ACP answer · many turns works in /app WHAT HARBOR KEEPS Decisions asked, answers, end ACP session every message and tool call App folder plan, app, git history → scored after the trial, see below
A trial is one scenario played once. A run plays each scenario several times with each model.

The scenarios

Each scenario starts in an empty folder with the build prompt of lernapps.net/apps/build: a quiz on fractions, explanation pages on electric circuits, and a small typing game. The assistant reads the guidance from lernapps.net, as any assistant would, at the version on main at that moment.

The simulated creator

The creator is Claude Code playing a teacher. Its persona says what the teacher wants, which facts they know, how they answer, and when they stop. It ends the scenario before anything goes online: at the question to publish, when the assistant says it is done, or when the assistant is stuck.

For each message the creator first decides, in a fixed format: what the assistant asked, what its reply answers, the reply itself, and whether this is the end. Only the reply reaches the assistant, which keeps talking to what looks like a person.

{"asked": ["plan_confirm"], "answers": ["plan_confirm"],
 "reply": "Ja, passt so.", "end": null}

Why the decision in a fixed format? In the first runs the creator made mistakes that counted as the assistant's: it ended a scenario at a question about GitHub, and it forgot to mark its end. We had to guess its intent from free text and fixed those guesses three times. Now its end and its yes to the plan are read, not guessed, and its own mistakes are counted apart: an answer to a question nobody asked, a yes without a question, a message sent without the command, a last message without its end.

What Harbor does, and what we built

Harbor is an open framework for running agents on tasks in containers. It does the heavy lifting. Our part is what makes it an eval of our guidance.

Harbor

  • builds the container of each task,
  • installs the assistant: Claude Code, OpenCode and many more,
  • runs the simulated creator and connects it to the assistant over ACP, an open protocol between a client and a coding agent,
  • keeps the logs, the ACP session and the app folder,
  • runs the verifier in a second container,
  • runs the same task in Docker, in Apple Container on a Mac, or in the cloud.

Built by us

  • the scenarios: build prompt, persona, expected kind of app,
  • the creator's command and its fixed format,
  • the verifier of the app folder,
  • the score of the conversation, with one small adapter per assistant,
  • the results of a run, the conclusions and this site,
  • two small patches to Harbor and a wrapper that keeps every other credential out of the containers.

Why Harbor? It runs a simulated user against a real assistant out of the box, for many assistants, in a standard task format. Building our own runner would have cost more than the evals themselves. The decision is recorded in the ADR on eval runs with Harbor.

What we score

Two scripts look at a finished trial from two sides.

SourceWhat it tells
The app folder
the verifier, in its own container
Is there a valid plan? Which phase did it reach, and how many of its boxes are ticked? Is the kind of app right? Does lernapps check pass? Is everything committed?
The conversation
the transcript score
Did it read the guidance in full or only a summary? How many questions in one message wait for an answer? Did it create the app before the plan's yes at the checkpoint? Does each reply begin with its phase? Did it load each skill in its phase? Which kind of app did it propose first? Did it bypass a git hook?

Each row on Prompt engineering measures one expectation of the guidance (A1 to A34 in the plan of the evals). We score the process, not the content: an app built by the process is good when its creator knows the subject.

Every assistant, one score

Each assistant writes its own kind of log. But Harbor saves the ACP session of every assistant it runs: each message of the creator, each text of the assistant, each tool call with what went in and what came out. The transcript score reads that session, so it works the same for every assistant.

Only the names of the tools differ. A small adapter per assistant maps them: OpenCode's bash becomes Bash, its filePath becomes file_path.

How we know it holds: for a Claude Code run, reading the ACP session gives the same 24 numbers and the same evidence as reading Claude Code's own log.

Adding an assistant takes an adapter in evals/harnesses.ts and one line in evals/harbor/run.sh.

From a run to a change

  1. Runan eval session in the cloud, with its own key Each scenario with each model, three times where the subscription allows. The session pushes only the numbers: results.json.
  2. Check the measure, right after the runpeople, with an assistant We read some conversations and check that each number measures what it should. Every run so far found a fault in the eval itself (the list); we fix it and score the run again. This has to happen while the conversations exist: the run keeps them in its own container, and only the numbers reach the repository.
  3. Concludepeople, in a pull request conclusions.yaml: the question, the bottom line, findings that link the cells they rest on, and what we do about them.
  4. Changeguidance or tooling Each finding becomes a short, hard rule where the step happens, or a check when a rule keeps being skipped.
  5. Run againthe same scenarios The next run compares its cells with the last one, on Prompt engineering.

Faults we found in our own evals

Each fault made a number say something about the assistant that was not true. We found them by reading the conversations behind the numbers.

Found inWhat the eval got wrong What we changed
First run
18 Oct
Harbor put its own config file into the app folder, and the app generator refused the folder. A patch to Harbor keeps the file out of the app folder.
First run The simulated creator stopped at a question about GitHub Pages while planning, as if it were the question to publish. The persona says which question ends the scenario; the run was left out.
First run Reading the setting of the git hooks counted as bypassing them. Only setting or removing it counts.
First run Files read in the shell (cat) did not count as read, so “skill read” was wrong. The row was left out of that run; the score now reads the shell's reads and downloads.
First run Every sentence with a question mark counted as a question, also sample questions inside a plan draft. Since 22 Oct only questions that wait for an answer count.
Second run
21 Oct
A phase set with sed, a one-liner or a helper script went unseen, so “app created before the plan” was wrong. The score reads these commands and the output of grep '^phase:'; runs 1 and 2 were scored again.
Second run Haiku looked “stuck in Code”: mostly it was the creator ending the scenario at the first GitHub question. The end of the scenario is measured as such (“Code done”).
Second run The creator's end was guessed from its free text; the guess needed three fixes. The creator marks its end, and since 22 Oct decides each message in a fixed format.
Second run A yes to a draft in Explore counted as the plan's yes, so apps created too early went unseen. Each reply begins with its phase; since 22 Oct only the yes at the Plan checkpoint counts.
Second run The verifier crashed on a broken link in the app folder and lost the numbers of one trial. It skips such a link.
Third run
21 Oct
The creator wrote its end only into its own notes, and once ended without sending its last message: the assistant was scored for the creator's mistake. Since 22 Oct the creator's own mistakes are counted apart from the assistant's.
Test run
22 Oct
A command that ticked a box naming a phase (“Set phase: code”) counted as setting that phase. Only a change of the phase line counts.
Test run “Kind of app chosen right” read the plan at the end, after the creator had asked for the right kind: a wrong first choice scored as right. The first kind of app the assistant proposes is scored too.
OpenCode run
22 Oct
OpenCode reports the tokens of only the last model call of each turn: 3,200 output tokens for a turn that wrote 45 questions. Its tokens are summed from its own record in the trial.
OpenCode run The creator recorded that the assistant asked nothing, though it had asked two questions, and said “go on”. Its record agreed with itself, so no mistake was counted. Such a turn is flagged for a person to read; a second model as judge is planned (#156).
OpenCode run The README of an app template counted as a read outside the guidance, though the guidance links it. Every link in the guidance of the run counts as guidance.

Two lessons hold across all of them: read the conversations before the numbers, and do it while they still exist.

What the runs cannot tell yet, and every run, are on Prompt engineering. The code and its README: evals/ on GitHub.

Back to Prompt engineering