Skip to content

Support desk

A sample app in the repository: apps/support-desk. It consumes jevlish from npm like any other application would.

Terminal window
cd apps/support-desk && npm install
npm start # run the pipeline, print the desk report
npm run plan # what would be sent, without spending anything
npm run trace # run, then dump every judgment behind each decision
npm run measure # test the vocabulary against labeled fixtures

The API key is read from the repo-root .env (TYPESAFE_API_KEY).

Four stages, each one expression:

  • Escalation policy: given(ticket).when(needsAttention).do(escalate).otherwise(leaveUnchanged).whenUncertain(flagForReview)
  • Priority queue: from(tickets).where(isOpen).and(reportsProblem).rankedBy(disruption).take(5)
  • Incident linking: given({ ticket }).chooseFrom(incidents).by("the incident that explains…").orNone(…)
  • Owner suggestion: given({ ticket }).chooseFrom(onCall).by("whose experience best matches…").orNone(…)

src/vocabulary.ts is the domain language. The samples on this site use the same one:

vocabulary.ts
import { means, scale } from "jevlish";
import type { Ticket } from "./support.js";
// A domain vocabulary. These are meanings, not prompts: named, composable,
// inspectable, and graded against fixtures like any other function.
export const blocked = means<Ticket>("the customer is currently unable to complete their task")
.including("a product failure prevents them from completing the task")
.excluding("they can complete the task despite inconvenience, or they are only asking a question")
.named("blocked");
export const saysResolved = means<Ticket>("the customer says the problem has been resolved").named("saysResolved");
export const threatensToLeave = means<Ticket>("the customer says they will cancel or switch providers").named(
"threatensToLeave",
);
export const isOpen = (t: Ticket) => t.status === "open";
export const needsAttention = blocked.unless(saysResolved).or(threatensToLeave).and(isOpen);
export const reportsProblem = means<Ticket>("the message reports a problem with the product")
.including("something in the product is broken, failing, or behaving wrongly")
.excluding("a how-to question, a feature request, or praise")
.named("reportsProblem");
export const disruption = scale<Ticket>("how much the reported problem disrupts the customer's work")
.from("Work continues normally; the problem affects appearance only")
.through("The task remains possible through a workaround")
.to("The task cannot be completed")
.named("disruption");

src/fixtures.ts holds labeled examples for blocked; npm run measure reports accuracy, coverage, and abstentions.

  • Closed tickets never reach the model. The code predicate under .and() folds the whole expression to false before anything is sent. npm run plan shows them as settled by code.
  • needsAttention is blocked.unless(saysResolved).or(threatensToLeave).and(isOpen). The shape matters: resolution qualifies only the blocked branch, so a churn threat escalates regardless.
  • “Search is slow but works” tends to land between the thresholds. It goes to review, never silently to otherwise.
  • Every chooseFrom returns one of your own objects, or null for orNone, or uncertain. Three different things, kept apart.