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.
“the app crashes every time i open an invoice and i have a demo in 10 minutes”
- 01routetriage38ms
ask vibe · choice
- bug0.81
- billing0.16
- vibes0.03
- 02gateis-urgent41msnoulp(blocked) = 0.92≥ 0.70pass
- 03emitpage on-call
you asked a yes-or-no question. you deserve a number.
“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…”
{ 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
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: {}