The Tenor Go SDK evaluates contracts locally via a wazero WASM bridge --- pure Go, no CGo, no system dependencies. The evaluator runs the same compiled .tenor.wasm artifact as every other SDK, with identical results guaranteed by the cross-SDK conformance suite.
Installation
go get github.com/riverline-labs/tenor/sdks/goRequires Go 1.21 or later. No CGo. No system libraries. The wazero runtime is a pure Go WebAssembly interpreter/compiler, so the SDK works everywhere Go compiles: Linux, macOS, Windows, ARM, containers, scratch Docker images.
NewEvaluator — Local WASM Evaluation
NewEvaluator loads a compiled contract and evaluates it in-process. No network calls, no API key.
Loading a Contract
package main
import (
"context"
"fmt"
"log"
"os"
tenor "github.com/riverline-labs/tenor/sdks/go"
)
func main() {
ctx := context.Background()
// Load from file path
evaluator, err := tenor.NewEvaluator(ctx, "./contracts/escrow.tenor.wasm")
if err != nil {
log.Fatalf("failed to load contract: %v", err)
}
defer evaluator.Close(ctx)
// Load from bytes
wasmBytes, err := os.ReadFile("./contracts/escrow.tenor.wasm")
if err != nil {
log.Fatalf("failed to read file: %v", err)
}
evaluator, err = tenor.NewEvaluatorFromBytes(ctx, wasmBytes)
if err != nil {
log.Fatalf("failed to load contract: %v", err)
}
defer evaluator.Close(ctx)
fmt.Println("Contract loaded successfully")
}Evaluating a Contract
result, err := evaluator.Evaluate(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": true,
"payment_amount": "5000.00",
"customer_tier": "premium",
},
Entities: map[string]map[string]string{
"Order": {"order_001": "pending"},
"Payment": {"pay_001": "cleared"},
},
})
if err != nil {
log.Fatalf("evaluation failed: %v", err)
}
for name, verdict := range result.Verdicts {
fmt.Printf("%s: value=%v stratum=%d\n", name, verdict.Value, verdict.Stratum)
}
// payment_ok: value=true stratum=0
// eligible_for_release: value=true stratum=1
fmt.Printf("evaluation took %v\n", result.EvaluationTime)
// evaluation took 412µsComputing the Action Space
actions, err := evaluator.ActionSpace(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": true,
"payment_amount": "5000.00",
"customer_tier": "premium",
},
Entities: map[string]map[string]string{
"Order": {"order_001": "pending"},
"Payment": {"pay_001": "cleared"},
},
})
if err != nil {
log.Fatalf("action space computation failed: %v", err)
}
for persona, personaActions := range actions {
fmt.Printf("--- %s ---\n", persona)
for _, action := range personaActions {
fmt.Printf(" %s (precondition_met=%v)\n", action.Operation, action.PreconditionMet)
for _, e := range action.EntitiesAffected {
fmt.Printf(" %s:%s %s -> %s\n", e.Entity, e.Instance, e.FromState, e.ToState)
}
}
}
// --- escrow_agent ---
// release_funds (precondition_met=true)
// Order:order_001 pending -> released
// --- buyer ---
// dispute_transaction (precondition_met=true)
// Order:order_001 pending -> disputed
// --- seller ---Action Space for a Single Persona
buyerActions, err := evaluator.ActionSpaceForPersona(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": true,
"payment_amount": "5000.00",
},
Entities: map[string]map[string]string{
"Order": {"order_001": "pending"},
},
}, "buyer")
if err != nil {
log.Fatalf("action space computation failed: %v", err)
}
for _, action := range buyerActions {
fmt.Printf("%s: precondition_met=%v\n", action.Operation, action.PreconditionMet)
}
// dispute_transaction: precondition_met=trueContract Metadata
meta := evaluator.Metadata()
fmt.Println("Entities:")
for _, e := range meta.Entities {
fmt.Printf(" %s: states=%v initial=%s\n", e.Name, e.States, e.Initial)
}
// Entities:
// Order: states=[pending approved disputed released] initial=pending
// Payment: states=[held released refunded] initial=held
fmt.Println("Personas:", meta.Personas)
// Personas: [escrow_agent buyer seller]
fmt.Println("Facts:")
for _, f := range meta.Facts {
fmt.Printf(" %s: %s\n", f.Name, f.Type)
}
// Facts:
// payment_received: Bool
// payment_amount: Money
// customer_tier: Text
fmt.Println("Contract hash:", meta.ContractHash)
// Contract hash: sha256:9f4a2b...Error Handling
import "errors"
result, err := evaluator.Evaluate(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": "yes", // Wrong type: expected Bool
},
Entities: map[string]map[string]string{},
})
var typeErr *tenor.TypeError
if errors.As(err, &typeErr) {
fmt.Printf("Type error on fact %q: expected %s, got %s\n",
typeErr.Fact, typeErr.ExpectedType, typeErr.ReceivedType)
// Type error on fact "payment_received": expected Bool, got Text
}
var undeclaredErr *tenor.UndeclaredFactError
if errors.As(err, &undeclaredErr) {
fmt.Printf("Undeclared fact: %q\n", undeclaredErr.Fact)
// Undeclared fact: "nonexistent_fact"
}Full Working Example: Contract Testing
package escrow_test
import (
"context"
"testing"
tenor "github.com/riverline-labs/tenor/sdks/go"
)
var evaluator *tenor.Evaluator
func TestMain(m *testing.M) {
ctx := context.Background()
var err error
evaluator, err = tenor.NewEvaluator(ctx, "./testdata/escrow.tenor.wasm")
if err != nil {
panic("failed to load contract: " + err.Error())
}
defer evaluator.Close(ctx)
m.Run()
}
func TestPaymentVerdictFiresWhenPaymentReceived(t *testing.T) {
ctx := context.Background()
result, err := evaluator.Evaluate(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": true,
"payment_amount": "5000.00",
},
Entities: map[string]map[string]string{
"Order": {"order_001": "pending"},
},
})
if err != nil {
t.Fatalf("evaluation failed: %v", err)
}
v, ok := result.Verdicts["payment_ok"]
if !ok {
t.Fatal("expected payment_ok verdict to be present")
}
if v.Value != true {
t.Errorf("expected payment_ok=true, got %v", v.Value)
}
if v.Stratum != 0 {
t.Errorf("expected stratum 0, got %d", v.Stratum)
}
}
func TestPaymentVerdictAbsentWithoutPayment(t *testing.T) {
ctx := context.Background()
result, err := evaluator.Evaluate(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": false,
"payment_amount": "0.00",
},
Entities: map[string]map[string]string{
"Order": {"order_001": "pending"},
},
})
if err != nil {
t.Fatalf("evaluation failed: %v", err)
}
if _, ok := result.Verdicts["payment_ok"]; ok {
t.Error("expected payment_ok verdict to be absent when payment not received")
}
}
func TestEscrowAgentCanRelease(t *testing.T) {
ctx := context.Background()
actions, err := evaluator.ActionSpaceForPersona(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": true,
"payment_amount": "5000.00",
"release_approved": true,
},
Entities: map[string]map[string]string{
"Order": {"order_001": "pending"},
},
}, "escrow_agent")
if err != nil {
t.Fatalf("action space failed: %v", err)
}
found := false
for _, a := range actions {
if a.Operation == "release_funds" && a.PreconditionMet {
found = true
break
}
}
if !found {
t.Error("expected escrow_agent to have release_funds available")
}
}
func TestBuyerCannotRelease(t *testing.T) {
ctx := context.Background()
actions, err := evaluator.ActionSpaceForPersona(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": true,
"payment_amount": "5000.00",
"release_approved": true,
},
Entities: map[string]map[string]string{
"Order": {"order_001": "pending"},
},
}, "buyer")
if err != nil {
t.Fatalf("action space failed: %v", err)
}
for _, a := range actions {
if a.Operation == "release_funds" {
t.Error("buyer should not have release_funds in action space")
}
}
}
func TestTerminalStateHasNoActions(t *testing.T) {
ctx := context.Background()
actions, err := evaluator.ActionSpace(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": true,
"payment_amount": "5000.00",
},
Entities: map[string]map[string]string{
"Order": {"order_001": "released"}, // terminal state
},
})
if err != nil {
t.Fatalf("action space failed: %v", err)
}
for persona, personaActions := range actions {
for _, a := range personaActions {
for _, e := range a.EntitiesAffected {
if e.Entity == "Order" {
t.Errorf("%s has action %s on terminal Order", persona, a.Operation)
}
}
}
}
}
func TestTypeErrorOnWrongFactType(t *testing.T) {
ctx := context.Background()
_, err := evaluator.Evaluate(ctx, tenor.EvalInput{
Facts: map[string]any{
"payment_received": "yes", // Wrong type
},
Entities: map[string]map[string]string{},
})
if err == nil {
t.Fatal("expected error for wrong fact type")
}
var typeErr *tenor.TypeError
if !errors.As(err, &typeErr) {
t.Fatalf("expected TypeError, got %T: %v", err, err)
}
if typeErr.Fact != "payment_received" {
t.Errorf("expected fact 'payment_received', got %q", typeErr.Fact)
}
if typeErr.ExpectedType != "Bool" {
t.Errorf("expected type Bool, got %q", typeErr.ExpectedType)
}
}Performance Notes
The wazero runtime compiles WASM to native code on first load (typically 10-50ms depending on contract size). Subsequent evaluations against the same loaded evaluator take microseconds. For high-throughput services, load the evaluator once at startup and reuse it across requests.
// Load once at startup
evaluator, _ := tenor.NewEvaluator(ctx, "./contracts/escrow.tenor.wasm")
// Reuse across goroutines — evaluator is safe for concurrent use
http.HandleFunc("/evaluate", func(w http.ResponseWriter, r *http.Request) {
result, err := evaluator.Evaluate(r.Context(), input)
// ...
})The evaluator is safe for concurrent use from multiple goroutines. No mutex is needed. Each call gets its own WASM execution context within the wazero runtime.