Skip to content

peira (peer-uh) · ai-native api testing

Your test plan compiles.
A deterministic runner executes it.

Intent compiler, deterministic runner, failure triage, and evidence ledger in a single CLI. Point peira compile and peira run at any REST API.

View on GitHub

No API key · No signup · Zero LLM at runtime · MIT

seed 42

run-start · the loop

How it works

AI-native testing is not old testing with a model bolted on — the division of labor changes. Follow one promise through the whole loop, artifacts included.

what you write

## Cancelling a shipped order
Cancelling an order that has already shipped
is refused with 409, and the order stays SHIPPED.

…that's the whole authoring surface — your agent handles the tagging.

what you get

  • an executable, reviewable case — 16 sections became 26 cases in the reference bed
  • 5 fresh seeded probes per run for every invariant section
  • Given/When/Then docs and a visual HTML run report, regenerated on demand
  • a trust ledger where this section earns applied, run by run
01

You describe the promise — plain English

Your whole job is the sentences. The tag is mechanical: your agent adds it (it's in the drop-in agent instructions), peira adopt adds it to existing documents, and untagged headings work anyway — ids derive from the text. The id just makes lineage survive rewording.

Human · agent tags

intent/orders.md

## Cancelling a shipped order                   ← you
<!-- peira: id=order-cancel-shipped kind=ac -->  ← your agent
Cancelling an order that has already shipped     ← you
is refused with 409, and the order stays SHIPPED.
api testing before ai

Humans hand-write test code

Humans write intent — plain markdown, one promise per section

Test code silently drifts from the spec

Every case records the intent it came from; when that text changes, the case is flagged and recompiled

Sleeps, retries, and flaky green

Seeded, deterministic verdicts — any failure replays exactly

A red build is a chore to diagnose

Failures come back triaged — bug | drift | flake — with evidence

peira render

Readable output

Cases are JSON with subset-matching semantics. One command turns the same case into Given/When/Then — or a full visual HTML run report with the observed request/response log attached to every failure.

PASSCASE-status-visible-001

GivenPOST /orders as alice — captures orderId ← body.id

WhenGET /orders/status?id=$orderId, polling until { status: "CONFIRMED" }

Thenthe response is 200, and the body matches { id: $orderId, status: "CONFIRMED" }

Finallyevery captured job is drained to a terminal state

From intent status-visibility @ ae5ab7a63816 — when the section's text changes, this case is flagged stale and recompiled.

--seed 42

Seeded test data

The seed is a run's replay number. Every “random” value is hash(seed, case id, key) — pure functions, nothing stored. Same seed, same service state → same verdicts. Any failure replays exactly.

seed
hash(42, CASE-status-visible-001, nonce) = u7fa659b70a
hash(42, CASE-status-visible-001, label) = uc5ce68469e
hash(42, CASE-isolation-001, nonce) = u00048b8088

peira triage

Failure triage

Infrastructure errors are routed away before the model sees anything — misreading an unreachable environment as a product bug is mechanically impossible. What remains is judged against the intent text, not the case.

BUG

The service broke its promise

Observed behavior contradicts what the intent requires. Comes back as a finding — expectation, observation — ready to file.

DRIFT

The promise moved

The case's encoding is violated while the intent's real requirements still hold. Comes back as the smallest intent-level diff — you approve or reject it.

FLAKE

The evidence is thin

A failure pattern suggesting nondeterminism. Comes back as a re-run prescription — with the seed, so the replay is exact.

case-verdict → applied | contradicted

Evidence ledger

Record an adjudicated run with peira evidence; read the standings with peira trust. A passing section logs applied — and so does a triaged bug, because the section did its job catching the violation. Adjudicated drift logs contradicted, with the reason quoted verbatim. Unadjudicated noise logs nothing.

sectionappliedcontradictedrunslast applied
plan#parallel-request-queueing3032026-08-29
plan#robustness3032026-08-29
plan#get-status2122026-08-29
plan#post-submit2122026-08-29

real rows — peira’s own committed ledger, 3 recorded runs

evidence · docs/findings/

Metrics

The bed in this repo re-implements a service as a test fixture. The 27 hand-written specs it ships with are the originals — written years earlier, for the service it re-implements.

LLM calls at runtime

0

CI needs no API key, no session — the exit code gates the merge

legacy specs re-expressed

27/27

the original hand-written suite, re-expressed in request, capture and expect — without dropping to custom code once, and with no sleeps

divergences surfaced

3

compiling its legacy test plan verbatim caught intent/implementation gaps — one never tested by anyone

external API, first try

5/5

compiled against a public third-party API; the bed config was one URL

triage agreement

87.9%

bug-vs-drift across 33 behavior changes planted in advance; zero schema refusals or injections

tests on the tool itself

316

run against the exact compiled artifact that ships — strict TypeScript, Linux and Windows in CI

to the first verdict

57ms

boot, load and validate the whole suite, issue a real request, report — no transform pipeline, no JVM, no browser

tool overhead per case

0.17ms

measured, not claimed (npm run bench) — what you wait for is your service, not the runner

run-end · 0 error

Get started

One markdown file and one URL is enough to start. The first run writes evidence; the third run starts earning trust.

No API key · No signup · Zero LLM at runtime · MIT

Read the docs — getting started, CLI reference, case anatomy