Skip to content

Attest v0.5.0 — The Alignment Release

Released: February 27, 2026

Attest v0.5.0 is the largest release since launch — 48 changes across the Go engine, Python SDK, TypeScript SDK, documentation, and examples. This release unifies all package versions to 0.5.0 and brings the TypeScript SDK to full feature parity with Python.

  • 2.4x faster JSON processing in the engine via segmentio/encoding
  • TypeScript SDK reaches parity — plugins, continuous eval, LangChain.js adapter, CLI, branded types
  • Production-grade test coverage — 90+ new tests across engine, Python SDK, TypeScript SDK, and vitest
  • Unified versioning — engine, Python SDK, and TypeScript SDK all at 0.5.0

The Go engine got a full performance pass. Evaluations that previously hit bottlenecks in JSON serialization and schema compilation are measurably faster.

Compiled JSON schemas are now cached in a sync.Map keyed by SHA-256 of the schema bytes. Previously, the engine created a fresh jsonschema.Compiler on every evaluation — identical schemas recompiled every time. With caching, the first evaluation compiles and all subsequent evaluations hit the cache.

Validate() previously marshaled the entire trace to JSON just to check its byte length. It now accepts a pre-computed traceSize parameter, computed once at the handler level. One fewer json.Marshal per evaluation.

The history store and embedding cache got three targeted fixes:

  • Single-query stddev — replaced 2 full table scans with a single SELECT COUNT(*), AVG(score), AVG(score*score) using the statistical identity σ = √(E[x²] - E[x]²)
  • Batch SQL eviction — replaced Go-side sort-and-delete with a pure SQL DELETE ... WHERE rowid IN (SELECT ... ORDER BY accessed_at LIMIT ?). No more loading every row into memory.
  • Deferred LRU writesaccessed_at updates are buffered in a sync.Map and flushed every 5 seconds or at 64 entries in a single transaction. Cuts SQLite write pressure on every cache hit.

All 13 hot-path files in the engine now use github.com/segmentio/encoding/json instead of encoding/json. It’s a drop-in replacement with identical API. Benchmark suite in attest-bench/ shows 2.4x faster JSON encoding/decoding at 500 steps.


The TypeScript SDK now matches the Python SDK’s feature set. Every major subsystem that existed only in Python is now available in TypeScript.

import { PluginRegistry, type AttestPlugin } from '@attest-ai/core';
const myPlugin: AttestPlugin = {
id: 'custom-validator',
async evaluate(trace, config) {
// Custom assertion logic
return { passed: true, score: 1.0, message: 'Valid' };
}
};
PluginRegistry.register(myPlugin);

The ExpectChain also gained a .plugin() method for Layer 8 assertions:

attestExpect(result)
.plugin('custom-validator', { strict: true });
import { ContinuousEvalRunner, Sampler, AlertDispatcher } from '@attest-ai/core';
const runner = new ContinuousEvalRunner({
client,
sampler: new Sampler(0.1), // Sample 10% of traces
alertDispatcher: new AlertDispatcher({
webhookUrl: 'https://hooks.example.com/attest',
slackUrl: 'https://hooks.slack.com/...',
}),
maxQueueSize: 1000,
});
await runner.start();
runner.submit(trace, assertions); // Non-blocking
await runner.stop();
import { LangChainAdapter } from '@attest-ai/core';
const adapter = new LangChainAdapter('my-agent');
// Use as BaseCallbackHandler with any LangChain.js chain
const result = await chain.invoke(input, { callbacks: [adapter] });
const trace = adapter.buildTrace();

IDs are now branded types that prevent accidental misuse:

import { traceId, assertionId, agentId } from '@attest-ai/core';
const tid = traceId('trc_abc123'); // type: TraceId
const aid = assertionId('ast_001'); // type: AssertionId
const gid = agentId('agent-1'); // type: AgentId
// Type error: TraceId is not assignable to AssertionId
const wrong: AssertionId = tid; // ✗ compile error

Assertion specs are now type-safe discriminated unions instead of Record<string, unknown>:

// Before: spec was Record<string, unknown>
// After: spec is a tagged union
type AssertionSpec =
| SchemaSpec // { type: "schema"; json_schema: object }
| ConstraintSpec // { type: "constraint"; ... }
| ContentSpec // { type: "content"; pattern: string; mode: "contains" | "regex" }
| EmbeddingSpec // { type: "embedding"; ... }
| LlmJudgeSpec // { type: "llm_judge"; ... }
| TraceSpec
| TraceTreeSpec
| PluginSpec;
Terminal window
# Scaffold a new test project
npx @attest-ai/core init
# Validate test suite
npx @attest-ai/core validate
# Cache management
npx @attest-ai/core cache stats
npx @attest-ai/core cache clear
# Version
npx @attest-ai/core --version

The TypeScript SDK now ships both ESM and CJS builds via tsup, with "sideEffects": false for tree-shaking:

// ESM
import { AttestClient } from '@attest-ai/core';
// CJS
const { AttestClient } = require('@attest-ai/core');

All adapters now use typed traceFromResponse() instead of the loosely-typed capture(...args: unknown[]).

All 45 ExpectChain methods have full JSDoc with @param, @returns, and @example tags. IDEs now show inline documentation for every assertion method.


Two new methods for trace-level performance assertions:

expect(result) \
.aggregate_latency_under(500) \ # All steps under 500ms
.all_tools_called(["search", "summarize"]) # Both tools were invoked

Correctness & Safety (from v0.4.2 → v0.5.0)

Section titled “Correctness & Safety (from v0.4.2 → v0.5.0)”

These fixes shipped incrementally and are included in v0.5.0:

Engine: Plugin result wiring (submit_plugin_result was discarding data), drift alert mutex (race condition on stdout writes), MaxStepPayload enforcement (1MB limit was defined but never checked), regex pattern length limits, assertion ID length validation.

Python SDK: OpenAI tool args parsed from JSON string to dict, Ollama tool call extraction fixed, Gemini token count extraction fixed, asyncio.get_event_loop()get_running_loop() at 4 call sites, async agent decorator support.

TypeScript SDK: delegate() parent trace ID wiring, full adapter test coverage (70 tests from zero).

Engine: Budget tracker wiring (ATTEST_BUDGET_MAX_COST), semaphore-based concurrent requests, configurable judge cache size (ATTEST_JUDGE_CACHE_MAX_MB), history store retention policy with automatic pruning.

Python SDK: Engine read timeout (30s default, configurable via ATTEST_ENGINE_TIMEOUT), bounded continuous eval queue (1000 default, ATTEST_CONTINUOUS_QUEUE_SIZE).

TypeScript SDK: Request timeout via Promise.race, simulation mode (ATTEST_SIMULATION=1).


  • Handler tests — 14 test cases covering validate_trace_tree, submit_plugin_result, query_drift, generate_user_message
  • Cache stress tests — 6 concurrent stress tests with goroutine fan-out, verified with -race
  • Pipeline integration tests — 7 tests with mock LLM provider covering L5/L6 evaluation, budget enforcement, and hard-fail gating
  • Python — 30 new tests: EngineManager lifecycle (start/stop/restart/crash recovery), budget enforcement, CLI subprocess tests
  • TypeScript — 38 new tests: vitest plugin unit tests (replacing placeholder), CLI tests
  • Total new tests this release: 90+

Four Python examples ported to TypeScript in attest-examples/:

  • quickstart-ts/ — layers 1–4 assertions
  • openai-adapter-ts/ — chat completion + tool calling
  • schema-assertions-ts/ — Layer 1 schema validation
  • content-assertions-ts/ — Layer 4 content pattern matching

VariablePurposeDefault
ATTEST_BUDGET_MAX_COSTMax cost per evaluation batchunlimited
ATTEST_JUDGE_CACHE_MAX_MBJudge cache size in MB100
ATTEST_HISTORY_MAX_ROWSHistory store max rows before pruningunlimited
ATTEST_HISTORY_MAX_AGE_DAYSHistory store max age before pruningunlimited
ATTEST_ENGINE_TIMEOUTEngine read timeout in ms30000
ATTEST_CONTINUOUS_QUEUE_SIZEMax continuous eval queue size1000
ATTEST_ENGINE_NO_DOWNLOADDisable automatic engine downloadunset

TraceAdapter.capture(...args: unknown[]) has been replaced with TraceAdapter.traceFromResponse(response, options?). If you implemented a custom adapter, update the method signature.

If you were importing from specific subpaths (e.g., @attest-ai/core/dist/...), the dist structure changed with the tsup migration. Use the package exports map instead:

// Before (may break)
import { AttestClient } from '@attest-ai/core/dist/client';
// After (stable)
import { AttestClient } from '@attest-ai/core';

Terminal window
# Python
uv add attest-ai@0.5.0
# or: pip install --upgrade attest-ai
# TypeScript
pnpm add @attest-ai/core@0.5.0 @attest-ai/vitest@0.5.0
# or: npm install @attest-ai/core@0.5.0 @attest-ai/vitest@0.5.0

The engine binary auto-updates — both SDKs will download the v0.5.0 engine on first run.


PRScopeDescription
#30Engine, Python, TypeScriptCorrectness & safety fixes (E1–E6, P1–P5, T1–T2)
#31Engine, Python, TypeScriptRobustness & timeouts (E7–E10, P6–P8, T3–T4)
#32EnginePerformance: schema cache, SQL optimization, segmentio/encoding (E11–E16)
#33Python, TypeScriptTypeScript parity: plugins, continuous eval, LangChain.js, branded types, CJS (T5–T12, P9)
#34EngineHandler, cache stress, pipeline integration tests (E17–E19)
#35Python, TypeScriptSDK lifecycle/budget/CLI tests, vitest unit tests, TypeScript CLI (P10–P12, T13–T14)
#36DocsREADME update to v0.5.0
#37ReleaseVersion bump to v0.5.0