Repository navigation
v1.0.0-rc.2
Pre-release
Pre-release
·
608 commits
to main
since this release
Second release candidate, themed "Evidence": check a trace's integrity, write behavior contracts over what the agent did, get statistical verdicts and judge budgets, and judge with OpenAI, Anthropic, Gemini or a local ACP agent.
Added
- Native Anthropic (Messages API) and Gemini (
generateContent) judges:ATTEST_JUDGE_PROVIDER=anthropicorgemini, with keys fromATTEST_JUDGE_API_KEY,ANTHROPIC_API_KEY, orGOOGLE_API_KEYthenGEMINI_API_KEY,ATTEST_JUDGE_MODELrequired, andATTEST_JUDGE_BASE_URLfor gateways. Each provider has its own rate limit (ATTEST_RATE_LIMIT_ANTHROPIC[_BURST],ATTEST_RATE_LIMIT_GEMINI[_BURST]); an outage isnot_evaluated: provider_unavailable.doctorreports the new providers and key sources. Seedocs/guides/judges.md. - Judge results record the provider in
judge_metadata.providernext tomodel; reports printjudge: <provider>/<model>. attest calibrate --persiststores labels keyed by judge provider and model (--judge-provider,--judge-model), so calibration is never pooled across judges.ATTEST_JUDGE_PROVIDER=acpruns judges locally through an Agent Client Protocol agent started byATTEST_JUDGE_ACP_COMMAND(for example a Claude Code or Codex ACP adapter) on the developer's own subscription. The engine refuses it whenCIor a CI vendor variable such asGITHUB_ACTIONSis defined. The agent runs in an empty temporary directory and its own process group, gets no file-system or terminal capability, and has every permission request rejected; it is not sandboxed, and the engine warns about this at startup. Every call, including simulated-user turns fromgenerate_user_message, is bounded byATTEST_JUDGE_TIMEOUT_S. The agent's environment loses provider keys and endpoint switches (OPENAI_API_KEY,OPENAI_BASE_URL,CODEX_API_KEY,ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN,ANTHROPIC_BASE_URL,CLAUDE_CODE_USE_BEDROCK,CLAUDE_CODE_USE_VERTEX,GEMINI_API_KEY,GOOGLE_API_KEY,ATTEST_JUDGE_*), and logs name only the agent executable. Rate limit:ATTEST_RATE_LIMIT_ACP[_BURST]. Seedocs/guides/judges.md.attest trace check <otlp.json>reports the integrity of an OpenTelemetry trace export (orphan spans, parent cycles, duplicate span ids, unpaired tool calls and results, each with its span id) and its evidence coverage (model spans without tokens, cost, message content or model; tool spans without call id or content), as text or--json, exiting 1 on integrity errors.attest trace convert <otlp.json>turns the export into an Attest trace. Conversion from OTLP now runs in the engine on the current OpenTelemetry GenAI semantic conventions; malformed input is rejected with an error naming the span. Seedocs/guides/trace-check.md.- Model-call budgets enforced by reservation:
ATTEST_MAX_JUDGE_CALLScaps judge requests andATTEST_BUDGET_USDcaps the spend of judge and embedding calls per engine session;evaluate_batchaccepts a per-batchbudget(max_judge_calls,max_cost_usd). Every judge call,repeatrun, bias probe, simulated-user message, retry and paid embedding call reserves one call and its worst-case cost (price × prompt bytes + price × output-token limit) before it is sent and is reconciled to the reported token usage, so concurrent assertions cannot overspend (a failed call keeps its reservation; a request without an output limit is capped at 1024 tokens); a call that cannot reserve is not sent and its assertion isnot_evaluated: budget_exhausted. Prices come from a built-in table (gpt-4.1,gpt-4.1-mini,gpt-4.1-nano,text-embedding-3-small,text-embedding-3-large) orATTEST_JUDGE_PRICE_IN/ATTEST_JUDGE_PRICE_OUT(USD per 1M tokens); a USD budget with an unpriced judge or embedding model, or with theacpjudge, is a startup error. Seedocs/guides/configuration.mdanddocs/guides/judges.md. evaluate_batchresults carryusage: model calls and the judge calls among them, input, output and cached tokens, reconciledcost_usd,retry_cost_usd,unpriced_callsandbudget_refused. Report-v2 accepts it assummary.usage; the engine's markdown report printsmodel calls · tokens (cached) · $ · retry cost. Judge results cost the same per token on every provider (Anthropic and Gemini judges previously reported $0).- Judges can abstain: built-in rubrics (now version
v2) ask for{"abstain": true, "reason": "..."}when the output lacks the evidence the rubric needs, and the assertion isnot_evaluated: judge_abstainedwith the reason. Only the structured field counts; no phrase in an explanation is matched. Abstentions are not cached, and abstainingrepeatruns and bias probes are left out. attest policy evaluate --baseline <tag>with aregression: {confidence, tolerance}rule (defaults 0.95 and 0.05) compares the pass rate of each assertion with at least 2 runs in both the report and the baseline (fixed-N trials) with the baseline using a Wilson interval; single runs keep theblock_on_regressionstatus comparison, and trial assertions it cannot compare areinconclusiveand returnsverdictpass,regressed(exit 1) orinconclusive(exit 2) with per-assertion intervals. Report-v2 rows accept a 1-basedtrialand test rows atrialscount. Seedocs/guides/verdicts.md.attest baseline pinstores each assertion's passes, evaluated runs and not-evaluated runs, folding the rows of fixed-N trials into one entry;baseline showprints them. The baselines table in$ATTEST_CACHE_DIR/attest.dbgains the columns on first use, and rows pinned earlier count as one run.- Fixed-N trials:
pytest --attest-trials=Nruns every test that uses theattestfixture N times, and the Vitesttrials()helper (N fromnorATTEST_TRIALS) runs a test body N times. During a trialevaluate()records without enforcing; the test fails when the pass rate of its evaluated trials is below the minimum pass rate (default 1.0, so any failing trial fails it;--attest-min-pass-rate/@pytest.mark.attest_min_pass_rate(...), VitestminPassRateonattestGlobalSetuportrials(), orATTEST_MIN_PASS_RATE), not-evaluated trials are left out of the rate, and a trial that raises counts as a failed trial whose rows are replaced by onehard_failrow per assertion. Report rows carrytrialand test rowstrials;attest policy evaluate --baseline <tag>with aregressionrule turns them intopass,regressedorinconclusive. Assertion ids inside a test are derived from the test id and the assertion's position, so they match across trials and runs. Seedocs/guides/statistical-verdicts.md. - Per-test budgets:
@pytest.mark.attest_budget(max_judge_calls=..., max_cost_usd=...)and the VitesttestBudget: { maxJudgeCalls, maxCostUsd }option ofevaluate/inspect(budgetontrials()) send the test's remaining budget with each evaluation; agent spend frommetadata.cost_usdis checked between trials only, and further trials stop once the test budget is spent. - The pytest and Vitest run summaries print
attest usage: <calls> model calls · <tokens> tokens (<cached> cached) · $<cost> · retry cost $<cost>, and reports record it insummary.usage. The SDKs know thebudget_exhaustedandjudge_abstainedreasons. - Trace schema version 2 (
proto/attest/v1/trace-v2.schema.json, fixtures inprotocol-tests/trace-v2/): optional conversationturns, agenthandoffs,conversation_id, and per-stepretry_of,error, recorded request/responseexchange,span_id,parent_span_idandtool_call_id. The engine accepts schema versions 0–2; version 1 traces are unchanged. - Behavior-contract trace checks in the engine (Layer 3,
type: "trace"):tool_cardinality(calls per tool, optionally per argument value, reported asissue_refund called 2× for order_id=O-1042 (max 1)),trajectory(strict,unordered,subset,superset,partial_order),tool_order,cycle(period 1–4),failure_streakandarg_predicate. They match tool calls only and count retried calls. Specs are listed inproto/attest/v1/protocol-spec.md. - Behavior-contract DSL in Python and TypeScript:
tool_cardinality/toolCardinality,trajectory,trajectory_partial_order/trajectoryPartialOrder,tool_called_before/toolCalledBefore,no_tool_cycles/noToolCycles,max_tool_failure_streak/maxToolFailureStreakandtool_arg_satisfies/toolArgSatisfies. Both SDKs emit identical specs, checked byprotocol-tests/contracts/dsl.json. Seedocs/guides/behavior-contracts.md.
Changed
- The
loop_detectiontrace check shares thetool_cardinalitycounter; its failure explanation now readsfetch called 3× (max 2). Its result status is unchanged. - BREAKING:
attest calibrate --persistrequires--judge-providerand--judge-model. The calibration table in$ATTEST_CACHE_DIR/attest.dbgainsjudge_providerandjudge_modelcolumns on first use; labels stored before have neither and are not counted for any provider. - BREAKING: The Python
OTelAdapterconverts spans through the engine (attest-engine trace convert) instead of its own mapping, so it needs an engine and returns trace v2. Tool steps takeargsandresultfromgen_ai.tool.call.arguments/gen_ai.tool.call.result(previouslycall_id,parametersandoutputfrom non-standard attributes; the call id is nowtool_call_id), a top-levelinvoke_agentspan becomes a conversation turn instead of anagent_callstep, and malformed spans raiseattest.exceptions.OTLPConversionError. An engine binary is required (the SDK downloads its engine version, or setATTEST_ENGINE_PATHto one built withmake engine).OTelAdapter.from_otlp()converts an OTLP/JSON export given as apathlib.Path, JSON text, bytes or a parsed mapping. Span events, span kind and resource attributes are not read.to_otel_spans()writes the current semantic convention attributes under aninvoke_agentroot span.Trace.to_dict()omitsparent_trace_idwhen it is unset. - BREAKING: The TypeScript
OTelAdapter.fromSpans()converts spans through the engine and returns aPromise<Trace>(trace v2) instead of aTrace. Spans needspanContext().spanId;parentSpanId(sdk-trace-base 1.x) orparentSpanContext(2.x) andstatusare read.OTelAdapter.fromOtlp()converts an OTLP/JSON export, and malformed input rejects withOTLPConversionError. Invoke-agent, retrieval andexecute_toolspans, previously dropped or misread, now convert as in Python.new OTelAdapter(agentId)is removed (use the static methods), spanattributesmust be OpenTelemetry attribute values, andfromSpans([])rejects (an export needs at least one span). TheStepandTracetypes gain the trace v2 fields. - Built-in judge rubrics are version
v2(abstain instruction added), so calibration labels stored againstv1no longer match them. repeat: Njudges take the mean of the two middle scores for an even number of scored runs (previously the upper middle score).- Gemini judge usage counts thinking tokens (
thoughtsTokenCount) as output tokens, as they are billed. - Judge and embedding calls stop when the batch's context is cancelled.
attest policy evaluate --baselinefolds the rows of fixed-N trials into one row per assertion (worst status, mean score, summed cost and duration) before computing transitions.- Judge cache entries are keyed by the whole judge prompt (rubric version, criteria and agent output) instead of the agent output alone. Two
passes_judgeassertions with different criteria on one output previously shared one cached score. Entries cached by earlier versions are misses and are judged again. - Cached judge scores are scoped by judge provider as well as endpoint, rubric and model, so two providers behind the same gateway with the same model name never share a score. Entries cached by earlier versions are misses and are judged again.
- Judge provider error messages have any echoed API key replaced by
[redacted]and control and Unicode format characters (bidi overrides, zero-width) removed before they reach results and reports. - A constraint on a trace field the trace does not record, such as
cost_underwithoutmetadata.cost_usd, explains itsnot_evaluated: missing_evidenceresult asmetadata.cost_usd absent. - The documentation website moved to
https://attest-framework.github.io/(repositoryattest-framework/attest-framework.github.io, formerlyattest-website). The release and SDK CI website dispatches target the renamed repository, andhttps://attest-framework.github.io/attest/redirects to the new root. The old/attest-website/path no longer resolves.
Removed
- The MkDocs site configuration (
mkdocs.yml) and the pages it built outsidedocs/guides/.https://attest-framework.github.io/attest/already redirects to the documentation website, which syncsdocs/guides/*.mdat each release.
Fixed
- Install instructions in the README,
docs/guides/merge-gate.md,docs/guides/framework-adapters.mdand the Python SDK README (the PyPI page) useuv add "attest-ai>=1.0.0rc1"instead ofpip install, andnpm install -D "@attest-ai/core@>=1.0.0-rc.1" "@attest-ai/vitest@>=1.0.0-rc.1". A barepip install attest-ai,uv add attest-aiornpm install @attest-ai/coreinstalls 0.9.0, the newest stable release, until 1.0.0 is published.
Published components
| Component | Status |
|---|---|
| Engine binaries and checksums (this release) | published |
PyPI attest-ai==1.0.0rc2 |
published |
npm @attest-ai/core@1.0.0-rc.2 |
published |
npm @attest-ai/vitest@1.0.0-rc.2 |
published |
| Website sync | dispatched (release-sync to attest-framework/attest-framework.github.io) |