skip to content
v0.1 · decision graphs that show their work

chains of thought,
minus the thought.

jevchain composes calls to jev, typesafe's classification model, into typed decision graphs. jev doesn't write essays — it answers your questions with probabilities, in milliseconds, and jevchain routes on them. every run leaves a full trace: each question, each distribution, every branch not taken.

trace · run_7f3a
illustrative
input

“the app crashes every time i open an invoice and i have a demo in 10 minutes”

  1. 01
    routetriage38ms

    ask vibe · choice

    • bug0.81
    • billing0.16
    • vibes0.03
  2. 02
    gateis-urgent41ms
    noulp(blocked) = 0.92≥ 0.70pass
  3. 03
    emitpage on-call
79ms · 2 callsnot taken: billing · vibes · file a ticket

you asked a yes-or-no question. you deserve a number.

a chatty llm2,341ms · 187 tokens

“Great question! Based on the content of the message, it appears the user may be experiencing an issue that could potentially relate to billing, although it's also possible that it…”

jev38ms · one call
{ billing: 0.92, bug: 0.06, vibes: 0.02 }

a distribution you can threshold, route on, log, and diff. jevchain turns a pile of these into a program.

decisions, composed like functions.

route branches on a choice. gate continues only when a probability clears the bar. nest them, reuse them, serialize them. the types follow the chain all the way down, so every branch you forgot is a compile error instead of a 3am page.

  • ask
  • route
  • gate
  • parallel
  • cascade
  • step
triage.ts
import { route, gate, choice, noul, emit } from "jevchain";

const triage = route("triage", {
  ask: choice("What is this message about?", {
    billing: "money, invoices, refunds",
    bug: "something is broken",
    vibes: "no actionable content, just vibes",
  }),
  branches: {
    billing: emit("→ billing"),
    bug: gate("is-urgent", {
      ask: noul("Is the user blocked right now?"),
      pass: { min: 0.7 },
      then: emit("page on-call"),
      otherwise: emit("file a ticket"),
    }),
    vibes: emit("reply with a gif"),
  },
});
  • 01

    typed answers

    choice options come back as a literal union, not string. your editor knows what jev can say before jev does.

    answers.vibe.choice // "billing" | "bug" | "vibes"
  • 02

    exhaustive routing

    add a fourth option and forget its branch, and tsc tells you before production does.

    // ✗ property 'refund' is missing in branches
  • 03

    streaming traces

    every run emits events as it happens: node entered, question asked, distribution received, branch taken.

    for await (const e of run.events) draw(e)
  • 04

    parallel + batching

    fan out asks concurrently. questions about the same input fold into a single jev call.

    parallel({ tone, topic, urgency }, merge)
  • 05

    serializable chains

    chains are data. json in, json out — the studio and your code share one definition.

    fromJSON(toJSON(triage)) // same chain
  • 06

    zero dependencies

    typescript and fetch. no four-hundred-package tree, no ‘agent’ that is secretly a while loop.

    dependencies: {}

stop generating.
start deciding.

api key

bring your own typesafe key, or ride the shared one (rate-limited, be nice).

shared key
checking…
your key
not set

your key stays in this browser (localStorage, jevchain.byok). it only travels to this site's /api/jev proxy in an x-typesafe-key header, which forwards it to typesafe and immediately forgets it. nothing is logged or stored server-side. requests on your own key get a much roomier rate limit.

keyboard shortcuts

fewer clicks, more chains. these work anywhere outside a text field.