Go harness for System One decision models: TypeSafe's hosted Jev, the open-weight, self-hostable Kev, and Cloudflare's Clef on Workers AI.
Decision models do not generate text.
They take a state (a string or structured JSON) plus a map of typed questions and return one calibrated answer per question in a single forward pass.
| Primitive | Ask | Get |
|---|---|---|
Bool |
yes/no | probability of yes |
Choice |
pick one of up to 255 options | chosen option + probability per option |
Score |
rate on an ordered rubric (2-10 levels) | interpolated score + probability per level |
The protocol layer is in the taigrr forks of fantasy and catwalk:
fantasy.EvaluationModel/fantasy.EvaluationProvider— the modality abstraction.fantasy/providers/vercel— Vercel AI GatewayPOST /v1/evaluate(modeltypesafe-ai/jev).fantasy/providers/typesafe— nativePOST /v1/systemoneonapi.typesafe.ai, or any wire-compatible server such as a local Kev.fantasy/providers/cloudflare— Clef and Clef-Flash on Cloudflare Workers AI (POST .../ai/run/@cf/cloudflare/<model>, Cloudflare envelope unwrapped, embedded images supported).fantasy/providers/kev— Kev fully in-process (llama.cpp via yzma), weights downloaded at runtime.catwalk.Provider.EvaluationModels— Jev listed under thevercelandtypesafeproviders; Clef undercloudflare.
gojev adds what an agent framework needs on top: catwalk-driven construction, typed decision helpers, the Kev conversion tooling, and a CLI.
go get github.com/taigrr/gojevimport (
"github.com/taigrr/fantasy"
"github.com/taigrr/fantasy/providers/vercel"
"github.com/taigrr/gojev"
)
provider, _ := vercel.New(vercel.WithAPIKey(os.Getenv("AI_GATEWAY_API_KEY")))
model, _ := provider.(fantasy.EvaluationProvider).EvaluationModel(ctx, vercel.ModelJev)
resp, err := gojev.Evaluate(ctx, model, "Shoes arrived two weeks late and I see two charges on my card.", gojev.Questions{
"department": gojev.Choice("Which team should handle this?", map[string]string{
"returns": "Exchanges, refunds, wrong or damaged items",
"shipping": "Delivery status, delays, lost packages",
"billing": "Charges, invoices, payment problems",
}),
"escalate": gojev.Bool("Does this need urgent human attention?"),
"frustration": gojev.Score("How frustrated is the customer?", "Calm", "Frustrated", "Very angry"),
})
resp.Answers["department"].Choice // "billing"
resp.Answers["escalate"].Yes() // true
resp.Answers["frustration"].Level() // "Frustrated"type Verdict string
const (Allow Verdict = "allow"; Ask Verdict = "ask"; Deny Verdict = "deny")
d, err := gojev.Classify(ctx, model, toolCall, "Should this run without asking?",
[]Verdict{Allow, Ask, Deny}, map[Verdict]string{Allow: "safe and reversible"})
if v, ok := d.Above(0.8, 0.3); ok && v == Allow { ... } // confident AND unambiguousAlso: gojev.AskBool(ctx, model, state, q).Above(threshold), gojev.Rate(...), gojev.Ask(...), gojev.Fallback(cloud, localKev).
model, err := gojev.FromCatwalk(ctx, provider, "") // uses provider.DefaultEvaluationModelIDWorks for catwalk entries of type vercel, typesafe, cloudflare and kev; $ENV API keys are resolved and $ENV references in api_endpoint are expanded (the stock cloudflare entry uses $CLOUDFLARE_ACCOUNT_ID).
Every hosted provider takes a base URL override, so any server that speaks the wire protocol can stand in for the hosted one:
typesafe.WithBaseURL— a localkev.serve, or any/v1/systemoneserver.vercel.WithBaseURL— a different AI Gateway deployment.cloudflare.WithBaseURL— the Workers AI run root (/@cf/cloudflare/<model>is appended), e.g. an AI Gateway route;cloudflare.WithAccountIDderives the default root from the account instead.
In catwalk terms this is api_endpoint; on the CLI it is -base-url.
p, _ := cloudflare.New(
cloudflare.WithAccountID(os.Getenv("CLOUDFLARE_ACCOUNT_ID")),
cloudflare.WithAPIKey(os.Getenv("CLOUDFLARE_API_TOKEN")),
cloudflare.WithGatewayID("default"), // optional: route through an AI Gateway
)
model, _ := p.(fantasy.EvaluationProvider).EvaluationModel(ctx, cloudflare.ModelClefFlash)Clef can also look at images (a Clef extension to the System One API): pass up to four embedded PNG/JPEG/WebP images via cloudflare.NewEvaluationOptions(&cloudflare.EvaluationOptions{Images: ...}) in EvaluationCall.ProviderOptions.
fantasy/providers/kev runs Kev entirely inside your process via llama.cpp (yzma, no cgo).
Nothing is embedded in binaries; weights and the llama.cpp shared libraries are fetched on first use and cached under $KEV_CACHE (default: the OS cache dir, kev/).
p, _ := kev.New(
kev.WithCheckpoint(kev.Checkpoint4B), // 0.8B fast on CPU; 4B default; 9B most accurate
kev.WithAutoLibraries(), // install pinned, verified llama.cpp if missing
)
defer p.(io.Closer).Close()
model, _ := p.(fantasy.EvaluationProvider).EvaluationModel(ctx, "") // free; loads on first EvaluateEverything that is downloaded is verified against a digest compiled into the Go module before it is written, loaded or executed:
- Weights:
manifestDigestspins the SHA-256 of each checkpoint'smanifest.json; every file in the bundle is then verified against that manifest. Corrupt or tampered files are re-fetched; an untrusted manifest isErrManifestUntrusted. - llama.cpp:
LlamaCPPVersionpins the release and the SHA-256 of its digest manifest (the same pin yzma ships). Every archive and every extracted file is verified against it. On every load the installed files are re-hashed; a tampered library isErrLibrariesCorruptand is neverdlopened. - Upgrades: bumping
LlamaCPPVersionmakes existing installs reportErrLibrariesOutdated; withWithAutoLibraries()the new release is staged, verified, and swapped in atomically, then the old one removed.
Backend selection is automatic (Metal on Apple Silicon; CUDA → ROCm → Vulkan → CPU elsewhere) and can be forced with WithProcessor or $KEV_PROCESSOR.
| Checkpoint | 1 q, short state | 3 q, ~100 tok | 8 q, ~100 tok | 1 q, ~1500 tok | 3 q, ~1500 tok |
|---|---|---|---|---|---|
| kev-0.8b (Metal) | 14 ms | 61 ms | 130 ms | 194 ms | 250 ms |
| kev-4b (Metal) | 45 ms | 205 ms | 423 ms | 1.2 s | 1.4 s |
| kev-9b (Metal) | 68 ms | 323 ms | 800 ms | 2.6 s | 2.6 s |
| kev-0.8b (CPU, 12P+4E) | 117 ms | 460 ms | — | 1.2 s | — |
| kev-4b (CPU) | 630 ms | 2.2 s | — | 5.7 s | — |
| kev-9b (CPU) | 1.1 s | — | — | — | — |
Multiple questions share one decode of the state (KV prefix cache), so cost grows with branch tokens, not state × questions.
On CPU-only machines prefer kev-0.8b; a Q8_0 4B bundle would roughly halve the 4B CPU numbers and is a convert.py --outtype q8_0 away.
Output matches Kev's Python server to two decimals on every fixture for all three checkpoints (tools/kev-convert).
A running kev.serve (Python) is still reachable through fantasy/providers/typesafe with WithBaseURL("http://127.0.0.1:8008").
go run ./cmd/gojev -provider vercel \
-state "rm -rf ./build && git push --force origin main" \
-q "verdict=choice:Allow without asking?;allow=safe,ask=needs confirmation,deny=never" \
-q "destructive=bool:Could this cause irreversible data loss?" \
-q "risk=score:How risky?;harmless,moderate,dangerous"-provider typesafe|vercel|cloudflare|kev|kev-server, -model picks the model or Kev checkpoint, -base-url overrides the endpoint for any provider, -state - reads stdin, -json-state sends structured state, -json prints the raw response, -zdr requests zero data retention (Pro/Enterprise gateway plans only).
Cloudflare reads CLOUDFLARE_API_TOKEN (or CLOUDFLARE_AUTH_TOKEN) and CLOUDFLARE_ACCOUNT_ID (or -account); -gateway/CLOUDFLARE_GATEWAY_ID routes through an AI Gateway, and -base-url/CLOUDFLARE_WORKERS_AI_URL replaces the run root entirely.
gojev -provider cloudflare -model clef-flash -state "Checkout is failing for every customer" \
-q "urgent=bool:Is this urgent?" \
-q "severity=score:How severe is the impact?;No impact,Minor,Major,Critical"The CLI is also published to npm as a thin launcher that pulls the binary for your platform:
npx @taigrr/gojev -version
npm i -g @taigrr/gojevgo test ./...
staticcheck ./...