Tenor provides official SDKs in three languages: TypeScript, Python, and Go. Each SDK supports local evaluation: the contract runs in your process with no network calls, no API key, and no platform dependency. The SDKs share a conformance test suite that guarantees identical evaluation behavior across all three.
Available SDKs
| SDK | Package | Local Runtime |
|---|---|---|
| TypeScript | @tenor/sdk | WASM (browser + Node) |
| Python | tenor-sdk | PyO3 native bindings |
| Go | github.com/riverline-labs/tenor/sdks/go | wazero WASM bridge |
Local Evaluation
The evaluator runs inside your process. You load a compiled contract, provide facts and entity states, and get back verdicts and an action space. No network calls. No platform dependency.
Local evaluation is a pure function: same contract + same facts + same entity states = same verdicts, every time. The evaluator never modifies state, never contacts external systems, and has no side effects.
When to use local evaluation:
- Unit and integration tests. Assert that specific fact combinations produce expected verdicts. No test infrastructure needed beyond the SDK.
- CI/CD pipelines. Validate contracts and run contract-level tests as part of your build.
- Client-side preview. Show users what actions are available before they commit (TypeScript WASM runs in the browser).
- Edge evaluation. Run contract logic in serverless functions, Lambda@Edge, or Cloudflare Workers.
- Offline support. Evaluate contracts when the network is unavailable.
- Performance-critical paths. Local evaluation takes microseconds, not network round-trip milliseconds.
// TypeScript — local evaluation, no network
import { TenorEvaluator } from "@tenor/sdk";
const evaluator = await TenorEvaluator.load("./escrow.tenor.wasm");
const result = evaluator.evaluate({
facts: { payment_received: true, payment_amount: "5000.00" },
entities: { Order: { order_001: "pending" } },
});
console.log(result.verdicts);
// { payment_ok: { value: true, stratum: 0 }, ... }Cross-SDK Conformance
All three SDKs share a single conformance test suite: a set of contracts, fact inputs, entity states, and expected outputs. The test suite covers:
- Verdict correctness. Every rule fires (or does not fire) as expected for the given facts.
- Stratum ordering. Higher-stratum rules see only lower-stratum verdicts.
- Action space completeness. Every legal operation for every persona is included. No illegal operations appear.
- Type coercion rejection. Invalid fact types are rejected, not silently coerced.
- Edge cases. Empty fact sets, all-terminal entities, flows with parallel branches, multi-stratum chains.
The conformance suite runs in CI for every SDK release. If a new version of any SDK produces different verdicts from the reference implementation, the release is blocked.
This means you can mix SDKs freely in your architecture. A Python service can evaluate locally, a TypeScript frontend can preview action spaces in the browser, and a Go service can evaluate in a microservice --- all against the same contract, producing the same verdicts for the same inputs.
Type Generation
The tenor generate command produces typed bindings from your contract, so your application code is checked against the contract schema at compile time:
# Generate TypeScript types
tenor generate typescript escrow.tenor --output ./src/generated/escrow.ts
# Generate Python types
tenor generate python escrow.tenor --output ./src/generated/escrow.py
# Generate Go types
tenor generate go escrow.tenor --output ./internal/generated/escrow.goGenerated types include:
- Fact types. A typed struct/interface for the facts your contract declares.
- Entity state enums. All states for each entity as a union/enum type.
- Verdict types. Names and payload types for each verdict the contract produces.
- Operation names. String literal types for every operation.
- Persona names. String literal types for every persona.
Using generated types turns contract violations into compile-time errors:
// Generated type ensures you cannot misspell a fact name or pass the wrong type
import { EscrowFacts, EscrowPersona } from "./generated/escrow";
const facts: EscrowFacts = {
payment_received: true,
payment_amount: "5000.00",
// typo: paymet_received — compile error
// wrong type: payment_received: "yes" — compile error
};Choosing Your SDK
| Scenario | Recommended SDK |
|---|---|
| Node.js backend service | TypeScript |
| React/Next.js frontend | TypeScript (WASM in browser) |
| Django/FastAPI backend | Python |
| Data pipeline / Jupyter | Python |
| Go microservice | Go |
| CI/CD contract testing | Any |
| Serverless / edge function | TypeScript or Go |
The right choice depends on your stack and your needs. All three SDKs produce identical results. Pick the one that fits your language and runtime.