Evals
Erdo evals score an agent’s work against a rubric, with pass/score tracking over time. They run in production and are designed to be driven by an AI coding agent (e.g. Claude Code via MCP): change how something is generated, run the suite, read the per-criterion scores, then add, rewrite, or remove cases as the generation changes. Two kinds of suite:- Text suites — judge the agent’s text answer against the rubric (e.g. the data-question-answerer).
- Artifact suites (
evaluate_artifact: true) — judge the rendered page the agent builds (landing pages, dashboards, apps). Erdo publishes the page, screenshots it on desktop and mobile, drives it in a real browser (submits the lead form, advances the carousel, confirms the voice widget loads, checks the conversion analytics event), and scores the screenshots + interaction with a vision model. A page that looks fine but doesn’t capture the lead scores low.
name, input brief,
rubric of weighted criteria), a judge model, and a pass_threshold greater
than 0 and at most 5 (omission defaults to 3.5).
Cases run through the same path a real user/API call hits — a thread, a message,
and the normal agent invocation — so an eval tests the actual product flow, not a
synthetic one. (External egress like email/integration writes is mocked during an
eval run; everything else is real.) A case may add setup_messages: ordered turns
run before input in the same thread, for multi-step flows — e.g. create a
voice widget, then build the landing page wired to it. The judge scores the
result of input.
Deployed-experience targets
Most cases invoke the suite’s agent. A case can instead set the closedtarget_type field to exercise an already-deployed experience. The seeded target
suites are excluded from the agent cron; the dedicated deployment/nightly workflow
runs them.
Targets are deliberately not open-ended. A case cannot provide a URL, widget id,
public key, organization id, or credential: the runner resolves trusted server-side
state. Page and widget fleet targets are global staff operations. Text, voice, and video
targets can resolve only the authenticated organization’s unique DB-designated
canary. Creating, editing, or running any deployed target requires staff access.
Authoring setup and targets
setup_messages belongs to an ordinary agent case. target_type bypasses the
suite agent and runs one of the five target executors above, so the two fields are
mutually exclusive.
Those bodies apply to
erdo_add_eval_case / erdo_update_eval_case over MCP and
POST / PUT /v1/evals/suites/{suiteSlug}/cases... over REST. On create, send
only one of setup_messages or target_type. On update, omission preserves the
stored field. clear_setup cannot be combined with setup_messages, and
clear_target cannot be combined with target_type.
Evaluators
A case can specify evaluators with two distinct roles, so an eval discriminates instead of passing everything:llm_rubric= the score. An LLM scores the output (or rendered page) against the rubric — the real signal; the case score is its weighted verdict.script= a gate, not a scorer. A JSevaluate(ctx)returning{score, passed, reasoning}, run deterministically (no LLM tokens).ctx = {input, output, artifact, data_store}. Use it for structural prerequisites (the lead form exists, the test lead actually landed indata_store). A clean gate adds no points; a failed gate hard-fails the case (structure missing = broken).
llm_rubric (a failed gate forces it to 0); a
script-only case scores on the gate. Any evaluator that errors fails the case —
never a silent pass. Set evaluators via --evaluator (CLI) or the evaluators field
(MCP); omit to use the rubric as a single llm_rubric.
CLI
--target-type accepts only live_page_fleet, widget_fleet_live,
widget_text_canary, widget_voice_canary, or widget_video_canary. For erdo eval create, put the
same target_type field inside each --case JSON object.
Widget canary contract
Text, voice, and video targets share one staff-designated canary in the pinned organization. Designation accepts a widget’s unique name or public key and resolves it only inside the authenticated organization. It requires an active text+voice+video widget, a provisioned provider agent, disabled scheduling, and a live provider configuration containing only the approved read-only knowledge and display-card tools. The runner revalidates the relevant modality and safety rules at execution time.show returns configured: false when the organization has no designation.
set and clear require --org (or ERDO_ORG) so another local session cannot
switch the machine-global active organization underneath a staff mutation.
The text target opens a fresh real session through the mobile widget’s visible input,
sends the case input, reads the rendered user/agent bubbles, then reuses the
returned session for a fixed continuity question grounded in the first answer. It
passes only when the authoritative transcript contains exactly those four ordered
user/agent turns and both rounds stay within their chat, persistence, and total
latency limits. Evidence includes stored turn timestamps and any model/token/tool
metrics present in the stored transcript; native text turns currently expose no
provider metric fields, so none are fabricated.
The voice target first opens the real mobile widget with greeting suppressed. A
shipping-image microphone fixture with seven seconds of setup silence says “Hello
Erdo”; the normal AudioWorklet uploads its PCM, provider STT must render that user
turn, a later agent bubble must render, and later response audio must be scheduled.
Because provider STT can legitimately spell the brand phonetically, the deterministic
recognition gate requires the stable words “voice” and “canary” rather than an exact
“Erdo” token.
Its exact authoritative session transcript must preserve that user-before-agent
order. It then sends the case input as a typed production-relay turn for stable
semantic judging. All of these are hard acceptance requirements, not advisory metrics:
- relay ready within 5 seconds, first audio within 15 seconds, agent transcript within 20 seconds, and the complete relay turn within 25 seconds;
- authoritative webhook transcript persistence within 90 seconds and the entire browser-plus-relay target within 5 minutes (a 45-second relay timeout is the typed turn’s outer failure guard);
- at least one audio frame containing even-aligned
pcm_s16leat 16 kHz, at least 3,200 bytes / 100 ms of audio, and at least 160 non-silent samples (absolute amplitude greater than 64); - the same canary widget/session plus exact user and agent text in the authoritative stored transcript.
Run source and production snapshot
Every run read/list response includestriggered_by and these optional revision
fields: commit_sha (the primary revision for the suite),
frontend_commit_sha, and backend_commit_sha. The admin run page links each
stored revision to its immutable GitHub commit; erdo eval results prints full
SHAs and erdo eval runs prints compact SHAs.
All supplied SHAs must be full 40-character hexadecimal Git object names. Run
requests accept source only for staff automation and only as post_deploy,
nightly, or rollback. Without it, an admin-console run records
triggered_by: "manual"; MCP/REST/CLI runs use the Knowledge surface and record
knowledge; scheduled runs record cron.
The deployed-experience workflow always records both frontend and backend
snapshot revisions. Its primary commit_sha is the frontend revision for landing
generation and deployed-page suites, and the backend revision for every widget-*
suite, so DevBot links the failure to the code most likely responsible without
losing the complete production snapshot.
input is the builder’s JSON input — artifact_kind
(landing_page | dashboard | app) plus a description.
An artifact suite builds real pages in whatever org the command resolves to. Once
every case is judged, the run cleans up the pages it built — they are soft-
deleted, so they stop appearing in listings and their public links stop working.
This keeps a later run of the same brand from discovering a leftover and editing
it instead of building fresh (which scores it zero). The cleaned-up pages stay
referenced from the run results and are restorable (erdo pages restore <id>) if
you want to inspect or keep one.
The active org set by erdo org use is machine-global and a concurrent session can
switch it, so erdo eval run refuses an artifact-building suite unless you pin
the org explicitly — pass --org <id|slug> (or set ERDO_ORG):
MCP tools
Available on the MCP server; identity and permissions ride your token, so you only see your organization’s and global suites.REST
Base URLhttps://api.erdo.ai, Authorization: Bearer YOUR_API_KEY.
The suite/case REST bodies use the same setup/target contract as MCP and the CLI.
For example, an update that switches an existing setup case to the text canary is:
"clear_target": true plus setup_messages. Sending only
clear_setup or clear_target removes that field. The incompatible pairs
clear_setup + setup_messages and clear_target + target_type are rejected.
The run body carries both attribution and the evaluated production snapshot:
cron_enabled: false) and run on demand.
