Support-ticket triage with Jev (TypeSafe AI): what real projects do when Jev fails, and a safe design
Routing support tickets to the right queue is a frequent concrete Jev use case: GitHub repository searches for "jev triage", "jev ticket" and "jev support" returned 272, 70 and 158 results on 1 Oct 2026, not all of them relevant. This page compares five public ticket-triage projects on one question: what happens to a ticket when Jev is unsure, returns an error, returns something malformed, has no key or hangs. Every cell comes from the project's source code at a stated commit. From that comes an eight-rule safe design.
Four of five ticket-triage projects read in source send unsure tickets to a person. The fifth ignores queue confidence, and on any Jev API error it files the ticket in its archive lane.
These are five projects out of at least 70 more triage repositories that were not read, so this is not a survey of how triage is usually built. None of the five takes a refund or account action from a Jev answer. None is independently verified as operating for real customers: two are helpdesk integrations (Zammad), three are demos or a command-line tool.
What happens to a ticket when something goes wrong
Open a situation to highlight its column in the table and read the safe pattern for it; opening one closes the others. Documented Read from source at the commits shown; README descriptions were not used.
Jev is unsureConfidence below the project's threshold
Four projects gate the queue choice on confidence and send the rest to a person: thresholds 0.5 (B), 0.55 (C), 0.6 (E) and 0.9 (D). A has no gate on its department choice: the chosen team is always used. Safe pattern: below your threshold, send the ticket to a person with the best guess and probabilities attached, as C does with an internal note.
API errorThe request fails with an HTTP error
Only D treats an error as "no decision": it returns 503, leaves the ticket unchanged and lets Zammad retry ("Never return a successful decision on model or API errors"). A files the ticket in its archive lane. B and C return 502 and leave the ticket unrouted (C without a note). E stops the whole batch. Safe pattern: hold the ticket for retry or a person; never map an error to a real queue.
Malformed answerMissing fields or values out of range
Only D validates the answer: the model id must match, the choice must be one of the options and the probability must be a finite number in [0, 1]. A does not validate, so a missing or non-numeric field ends in archive. B, C and E raise an error. Safe pattern: validate before acting and treat a failed check as an error.
No API keyThe key is not set
B, C and E refuse to start or exit. A returns HTTP 500 per request. D returns 503 so the source retries. All five fail visibly; none silently skips Jev. Safe pattern: fail loudly at start-up.
Jev hangsNo answer within a timeout
Two of five set no timeout (A uses a bare fetch, C uses HTTParty's default). D sets 20 s; B and E rely on the official SDKs' defaults. Safe pattern: set a timeout you chose, or know your SDK's defaults (see errors and rate limits).
| Project (commit) | Jev unsure | API error | Malformed answer | No key | Timeout | Sent to Jev |
|---|---|---|---|---|---|---|
A. CyrilBaah/jev-triage-desk (63913177)Runnable demo (Next.js); direct fetch to /v1/systemone, jev-latest. Tests: none | no-threshold: no gateThe chosen team is always used; lanes by spam, escalate and urgency scores | fallback-archive laneException caught; reason "error" | fallback-archive laneNo validation; a missing or non-numeric field ends there | fail-closed: HTTP 500"TYPESAFE_API_KEY is not set" | None setBare fetch | Ticket body. In the agent lane, subject and body also go to Groq for a draft reply |
B. tuhinmitra888/ai-jev-ticket-triage (3e8b83a8)Runnable demo; official JS SDK @typesafe-ai/sdk. Tests: 6 policy tests, no network | held: < 0.5 → human triageOption other → human too | fail-closed: 502, not routed"triage failed" | fail-closed: throws → 500No runtime check | fail-closed: server does not startClient built at module load | SDK defaultNot set in code | Subject, body; customer plan kept local |
C. enderkus/zammad-jev-dispatcher (f999eca7)Zammad helpdesk integration; HTTP POST, jev-latest. Tests: none | held: < 0.55 → stays "Unclassified"With an internal note: best guess and probabilities | fail-closed: 502; stays UnclassifiedNo note | fail-closed: raises → 502Unknown choice → manual-triage note | fail-closed: does not startRaises at boot | None setHTTParty default | Title and body, HTML stripped; customer not sent |
D. gbesse/zammad-jev-triage (5d2a0a78)Zammad helpdesk integration; pinned model jev-1.13.0. Tests: 3 test files and CI | held: < 0.9 or other → reviewMapped to a review group | fail-closed: 503; source retriesTicket unchanged | fail-closed: validated → 503Model, option and probability checked | fail-closed: 503; source retries | 20 sSet explicitly | Title and body, HTML stripped, at most 16,000 characters; public articles only |
E. veermshah/jev-support-desk (aef8fa0c)Command-line tool; official Python SDK. Tests: 7 test files and CI | held: < 0.6 → triage reviewNoul answers in 0.35–0.65 flagged "uncertain" | fail-closed: batch abortsExit code 2; one failure stops the run | fail-closed: raises → exitTyped SDK response | fail-closed: exit code 2 | SDK default10 s per attempt, 30 s budget | Subject and message; tier and routing metadata kept out |
- fail-closed or held (a person or the host's own prompt decides)
- fallback-<what> (named in the cell)
- not recorded, not applicable or no-threshold (the cell says which)
Colours follow the shared legend on when Jev fails (changed ; this page used its own colours before). The Timeout column is a setting, not a verdict, so it is uncoloured.
What each project does automatically: A assigns a lane and shows an LLM-drafted reply (no send code found); B sets queue, priority and flags, and a refund request is only a flag; C and D change the Zammad group (C also posts an internal note); E sets labels and suggested actions only. Not one of the five refunds, cancels or changes an account from an answer.
Where each project draws the line
Queue-confidence threshold below which a ticket goes to a person
DISPATCH_CONFIDENCE_THRESHOLD (default shown). B also adds a review reason when urgency confidence is below 0.4. A routes on spam, escalate and urgency scores (lanes at 0.6, 0.6, 2.5 and 1.0) but never checks how sure Jev is of the department. How to choose your own is on confidence thresholds. DocumentedA safe design, step by step
What your code should do with each Jev answer
- 1. Did the call fail? (no key, timeout, HTTP error)Yes: Hold the ticket for retry or a personNever map an error to a queue, archive or spam lane (D's 503 pattern)
- 2. Is the answer malformed? (missing key, option not in your list, probability outside [0, 1], wrong model)Yes: Treat it as an error and holdValidate as D does
- 3. Is the queue "other", or its confidence below your threshold?Yes: Send it to a person with the best guessAttach the probabilities, as C's internal note does; the five use 0.5–0.9
- OtherwiseApply the labelQueue and priority only; never a refund, cancellation or account change
Eight rules, and which projects follow them
- An error is not a decision.DA archives
On a missing key, timeout, HTTP error or malformed answer, hold the ticket for retry or a person. Never map it to a real queue, an archive lane or spam.
- Gate the queue choice on confidence.BCDEA: no gate
Below your threshold, send the ticket to a person with the best guess and probabilities. Tune the threshold on your own labelled tickets.
- Offer an explicit "other / unclear" option and treat it as review.BDA, C, E: no "other"
Tickets with no intent, or several, need somewhere to go.
- Validate before acting.DA: none
The answer has the expected keys, the choice is one of your options, the probability is a finite number in [0, 1], and the model is the one you pinned.
- Set a timeout you chose.D: 20 sB, E: SDKA, C: none
Or know your SDK's defaults (errors and rate limits).
- Send only what the decision needs.BDE
Subject and body, HTML stripped, length capped. Keep customer tier, IDs and contact details in your code. If a second model drafts replies, it receives the ticket too (A sends it to Groq). See what data leaves your system.
- Jev answers set labels, never irreversible actions.All five
None of the five refunds, cancels or changes accounts from an answer. Keep it that way.
- Test the routing policy offline.BDEA, C: no tests
Write it as a pure function and test it without the network, including every failure branch (testing without a key).
Cost per 1,000 tickets depends on your token counts: use the cost per decision worksheet. No new prices were collected for this page.
How accurate is Jev at customer-service routing?
There is no independent ticket-accuracy test. The closest published figure is TypeSafe's own WorkflowEvals customer_service workflow (204 cases, "Customer-service routing and actions under three policies"). It scores agreement with LLM reference answers (GPT-6 Astra and Claude Fable 5.1), not accuracy against human-labelled tickets. Reported Graded as B01 on the benchmarks ledger.
TypeSafe lists Jev at $0.0001 per case and 0.4 s on this workflow. Read on evals.typesafe.ai, 1 Oct 2026, 05:35 UTC. A vendor's own test: use it as a starting point for your own labelled sample, not as ticket accuracy.
How the five were chosen
Three GitHub repository searches on 1 Oct 2026 ("jev triage", "jev ticket", "jev support": 272, 70 and 158 hits). From the results, five support-ticket projects with a real Jev call path were pinned to one commit each and read from source. No code was installed or run and no Jev call was made. Excluded: repositories that compare Jev with LLMs on synthetic tickets (for example benkohcc/jev-ticket-triage), a project using OpenJev instead of hosted Jev, and one using a local "jeff" sidecar. Several other repositories share common names such as jev-triage and were not read.
The same failure questions are asked of six moderation projects on content moderation (holding, removing and appeals) and of six model routers on model routing. Demo videos of ticket triage are listed on the videos page; the worked ticket example on What is Jev? explains a single request.
What was not verified
- Live operation of any of the five projects: all are code-backed, none independently verified as operating.
- The official JS SDK's default timeout (project B relies on it).
- The remaining 70 or more triage repositories found by search.
- Accuracy of any project's routing: no project publishes it, and the pool ran none.
Sources and check times (1 Oct 2026, UTC)
- Projects, read at pinned commits (05:32): A (
lib/jev/*.ts,lib/router.ts,app/api/triage/route.ts,lib/agent.ts); B (src/triage.ts,src/server.ts,test/decide.test.ts); C (app.rb,config/groups.yml); D (jev_core.py,app.py,webhook.py,policy.json); E (triage.py,cli.py,tickets.py,taxonomy.py). - Search: GitHub repository search for "jev triage", "jev ticket" and "jev support" (05:32).
- Evidence box: evals.typesafe.ai and the WorkflowEvals README at commit 0ac3b8a (05:34).
- SDK timeouts: Python SDK defaults as recorded on errors and rate limits.