Skip to content

Add agent evaluations and Gadget durability tests - #268

Closed
AshishKumar4 wants to merge 8 commits into
mainfrom
wip/agent-eval-harness
Closed

Add agent evaluations and Gadget durability tests#268
AshishKumar4 wants to merge 8 commits into
mainfrom
wip/agent-eval-harness

Conversation

@AshishKumar4

@AshishKumar4 AshishKumar4 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Adds an evaluation suite that measures what the Workshop agent delivers, and integration tests that
pin the platform behaviour the suite depends on.

docs/testing-strategy.md describes the whole design. This description gives the summary.

What this adds

  • packages/workshop-evals — 8 tasks. Each task gives the agent a prompt, then calls the RPC of the
    Gadget that the agent built. A task scores the result, and never the method.
  • packages/integration-tests — two new capabilities on AgentSession. seedGadget() writes
    hand-authored source, so a test can use a known implementation. restartGadgets() restarts every
    Gadget server.
  • packages/integration-tests/__tests__/gadget-durability.test.ts — 4 tests. They run in pnpm test,
    they need no model, and they finish in 5 seconds.
  • packages/workshop-agent-cli — runs one prompt locally, without a browser.
  • .github/workflows/workshop-evals.yml — runs the evaluations each night and on demand.

Scoring

The score is the fraction of checks that passed. Nothing inspects the tools that the agent called.

One task asks for an appointment desk that never sells more places than it has. The agent passed it
with one synchronous SQL sequence and a database trigger. A check for a mutation queue would have
failed that correct answer.

The tool calls, the errors, the turns, the tokens, the time, and the cost are diagnostics. They explain
a result, and they never decide it.

A trial is invalid when we cannot attribute its result to the agent. The summary excludes an invalid
trial from every rate, and counts it in a separate column. A rate limit therefore reads as missing
data, and not as a regression.

Gates

Gate 1 Gate 2
Trigger every pull request each night, on demand, or the run-evals label
Command pnpm build, pnpm test, pnpm lint pnpm eval:required, pnpm eval:frontier
Blocks a merge yes only with the label

A required task fails Gate 2 when its score is below 1. A frontier task records its score and
never fails. We set the state from measurement. A new task starts as frontier.

Verified

  • pnpm build, pnpm test, and pnpm lint pass. 76 unit tests, plus the 4 durability tests.
  • A naive booking implementation oversells. Therefore the concurrency check can fail.
  • A restart keeps storage and drops memory. It holds across 5 restarts, and across a restart that
    interrupts a write.
  • 5 tasks passed a live run against @cf/zai-org/glm-5.2. One task passed one trial in three.
  • 33 task constants match a reference implementation. This found one wrong assertion in a task.

Not verified

  • Two tasks have no live measurement. Repeated attempts hit the Workers AI rate limit. Both tasks
    carry the frontier state, so they cannot fail the job.
  • I verified restartGadgets() on the mainline branch only. On a chat branch, stock-ledger proves
    the restart before it trusts it, so a silent failure reports itself.

One run against an account with fresh quota closes both:
WORKSHOP_EVAL_TRIALS=3 pnpm eval:frontier.

Draft

I opened this as a draft. It needs a baseline run with 10 trials and 2 models. That run decides the
state of each task.

AgentSession drives the production agent over the same Cap'n Web RPC the
browser uses: fresh account and workspace, one chat across turns, waiting
through callback-driven restarts, and reading complete paginated history.
It owns transport lifecycle only, so evaluation semantics stay with the
consuming suite.

startHarness() gains enableGadgetExecution, keeping the checked-in Worker
Loader so an agent can run executeCode. It defaults to false, leaving the
existing observer suites unchanged.
Gives the production agent a prompt in a real workerd Workshop, then
verifies the Gadget it built by calling that Gadget's own RPC. Scoring is
deterministic and outcome-only: nothing asserts which tools the agent
used or in what order, so a working result reached by an unexpected route
passes. Tool failures, agent errors, tokens, time and cost are recorded
as diagnostics and never decide a verdict.

Defining an eval is a task file plus a registry line, verified by a test
so the two cannot drift. A task is prompts and checks; the framework owns
Gadget lookup, evidence normalization, and isolating a throw to the check
that caused it. Repeated trials are summarized with pass rates, Wilson
intervals, and per-trial cost, with unmeasured telemetry reported as
absent rather than zero.

workshop-agent-cli runs one prompt through the same session driver, for
iterating on the agent without a browser.

The two packages share one lockfile change, so they land together.
Sharded across jobs, required and frontier reported separately, with
frontier never gating. Kept out of CI: each trial boots workerd and
spends real inference.
The first three tasks were API-shape smoke tests. These are applications
people actually build: a double-entry household ledger with cent-exact
splitting and settlement, a pantry that turns recipes into a shopping list
across mass and volume units, an appointment desk that must not oversell
under concurrent booking, flashcards on an SM-2 schedule, and a freelance
timesheet with half-open overlap and billing rounding.

Grounded in what the platform can really do: no gadget-to-gadget bindings,
no network, no client-side frameworks, no wall clock. Every one is
self-contained, driven entirely through its own RPC, and told the date it
is reasoning about.

Verification leans on invariants over hand-computed constants where it can
-- balances summing to zero, transfers clearing them, shares totalling the
expense -- because an invariant survives an author's arithmetic slip and
still catches the bug it describes. The constants that remain were checked
against a reference implementation first, which is how a wrong assertion
in the ledger was found sitting next to correct values.

Tiers now follow measurement. Five tasks passed every trial and gate CI;
the timesheet passed one of three, failing the same two things both times,
so it is frontier with the observed gaps recorded on it.

Two harness fixes came out of running these: an agent that errors without
ever calling a tool is unscored rather than scored zero, so an expired
token cannot masquerade as a bad model; and the summary refuses to write
over the report it is reducing.
The suite asserted an agent-built booking gadget never oversells under
concurrent load, but never showed that a bad implementation would be
caught. It now does: a seeded gadget written the obvious wrong way -- read
the count, check capacity, await, then insert -- oversells here, so the
check has teeth rather than passing for free.

Seeding is the new capability behind that. AgentSession.seedGadget() writes
hand-authored source straight into the workspace, so a test can pin
platform behaviour with no model involved. gadget-durability.test.ts uses
it to establish what a restart does: storage survives, memory does not, and
outstanding stubs are invalidated -- which is the only honest way to prove a
restart happened at all. It holds across five consecutive restarts and
across one landing on an unfinished write. Deterministic, free, and part of
pnpm test.

verifier.restart() exposes the same abrupt restart to eval tasks, because
on this platform a Gadget's server goes away on every code change rather
than only when something breaks. stock-ledger asks whether an app's state
was really persisted: it restarts between appends, immediately after one,
and mid-write, and re-checks duplicate rejection afterwards -- an id index
kept in memory passes until the first restart. It proves the restart was
real before trusting any of that.

project-doc covers the standard output formats, which nothing exercised
before: asked for a doc, the platform should hand back a real Doc from the
shipped blueprint, not a bespoke imitation. Verified as an outcome -- the
workpiece is presented as a document output and answers the Docs contract.
The harness now waits for formats to install before the first prompt, since
that install races the session and silently changes what the agent is told.

Scoring got more honest on the way. A failing trial whose turn ended with
an error is left unscored: the platform posts an error when a turn dies and
never for running out of turns or giving up, so it is almost always
infrastructure, and such a failure cannot be pinned on the agent. A trial
that passed despite one is still scored. Both new tasks start as frontier
because neither has a live baseline yet -- repeated attempts hit Workers AI
rate limiting, which is also why CI now caps shard concurrency.
Four suites now cover more than one module at a time, and the canonical doc
described two: it predates both workshop-backend/__integration__ and the
eval package. Omitting them is what makes someone rebuild a capability that
already exists, or fight a runtime that cannot reach what they need.

The dividing line is in-process versus out-of-process workerd, and it
decides what is available. Only the in-process suite has cloudflare:test,
so aborting one Durable Object or asserting on a native RPC boundary can
only happen there. Only the out-of-process suites get real transport,
several bound Workers, and a Worker Loader running Gadget code.

That is also why resetting looks different in each and neither approach
ports, which is worth stating where someone will read it before deciding
the eval harness should have used abortAllDurableObjects.
States the approach to tests above the unit level in one place: why we
separate integration tests from evaluations, the properties we want, the
four suites and what each runtime can reach, how we score a result rather
than a method, and the two gates.

Written in Simplified Technical English (ASD-STE100).
Replaces the aspirational framing with the mechanisms: the entry points of
the toolkit, the six steps of an evaluation run, the exact conditions that
make a trial invalid, the triggers and commands of each gate, and the
figures the summary reports.

Corrects one error in the first draft: incomplete telemetry does not make a
trial invalid. It only removes that trial from the token, duration, and cost
figures.
@github-actions github-actions Bot added the delivery Changes to CI or release delivery label Aug 19, 2026
@AshishKumar4

Copy link
Copy Markdown
Contributor Author

Replaced by a stack, so the toolkit can be reviewed apart from the suite:

I also dropped the agent CLI from the change. Nothing in the suite used it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

delivery Changes to CI or release delivery

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant