@jkudish/jev-mcp 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +429 -18
- package/dist/index.js +1021 -58
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +150 -0
- package/dist/lib.js +200 -0
- package/dist/lib.js.map +1 -1
- package/dist/provider.d.ts +5 -1
- package/dist/provider.js +371 -59
- package/dist/provider.js.map +1 -1
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
- Transport resilience on the fetch-based transports (OpenRouter, Cloudflare, and the Jev-compatible endpoint): bounded jittered retries on 408, 409, 429, and 500 through 599 only; ambiguous network-level failures (connection reset, TLS errors) never retry, because without an idempotency key a re-send can double-process a paid call, and neither do caller cancellation, deadline expiry, non-retryable statuses, unparseable bodies, or oversized bodies. A whole-request deadline (`JEV_MCP_REQUEST_TIMEOUT_MS`, default 60000) covers every attempt and cuts backoff sleeps short; a 1,000,000-byte ceiling is enforced while the body streams rather than after buffering, on success and error bodies alike; MCP client cancellation is wired through to the HTTP request; and secret redaction covers every Cloudflare error path (HTTP status, `success:false`, non-Completed state) plus OpenRouter error bodies. The typesafe transport routes its SDK through standalone undici (`undici` 7.x) so wired-through cancellation cannot trigger SDK 0.6.0's open process-crash bug on Node 20/22 ([typesafe-sdk-js#2](https://github.com/typesafe-ai/typesafe-sdk-js/issues/2)), guarded by a child-process regression; the vercel transport remains SDK-owned, and no uniformity is claimed for either. New env overrides `JEV_OPENROUTER_BASE_URL` and `JEV_CLOUDFLARE_BASE_URL` point those transports at a private gateway. Via the research in [issue #23](https://github.com/jkudish/jev-mcp/issues/23) by oppih.
|
|
6
|
+
|
|
7
|
+
- `jev_review` and `jev_gate` now preserve and validate score answer distributions, and both report why they decided: `reason_codes` covers every action path (unknown confidence, confidence or `safe_to_apply` below `review_at` or `auto_accept`, composite below floor, truncated context, invalid response, accepted) and `limiting_rubrics` names the rubrics that bound the decision, ties and null-confidence rubrics included. A reported distribution is returned in `scores.*.probabilities` and must be exact-key, sum to one, and agree with the reported score within a named tolerance (`SCORE_MEAN_TOLERANCE`, a validation choice of this package, not an upstream guarantee). Behavior change: a distribution that is present but malformed or contradictory previously passed unnoticed and now marks that rubric `invalid_response`. The Vercel adapter keeps an absent distribution absent instead of synthesizing `{}`, so "not reported" stays distinct from "reported malformed". Via [PR #20](https://github.com/jkudish/jev-mcp/pull/20) by deadczarvc, with the absent-versus-empty distinction, the named tolerance, and the reason-code coverage reworked on top.
|
|
8
|
+
- Tool inputs are strict: all ten tools now reject unknown top-level arguments and unknown keys inside fixed nested input objects (evidence items, candidates, classify items and classes, decide candidates, extract fields) instead of silently stripping them, and advertise `additionalProperties: false` in their JSON schemas. A typo now surfaces as a client error rather than a tool running with the argument missing. `context` remains an intentionally open record. Via [PR #19](https://github.com/jkudish/jev-mcp/pull/19) by deadczarvc.
|
|
9
|
+
- Every tool now fails closed on a null or non-object `answers` envelope: the `askJev` wrapper normalizes such payloads to `{}` once for all ten tools (`jev_classify`, `jev_decide`, `jev_rerank`, `jev_compare`, `jev_extract`, `jev_review`, and `jev_gate` previously indexed the envelope directly and crashed with a TypeError into a tool error); each tool then reports its structured `invalid_response` results, the same path verify, screen, and find already had. The default typesafe SDK transport passes such payloads through without rejecting them, so this normalization is the real backstop.
|
|
10
|
+
- `jev_extract` field validation now uses the shared float-safe probability-sum tolerance instead of a bare `0.01`: a three-way 0.33 / 0.33 / 0.33 distribution, whose float sum differs from one by 0.010000000000000009, is accepted like every other tool already accepted it.
|
|
11
|
+
- `jev_verify`, `jev_screen`, and `jev_find` fail closed on missing or malformed model answers instead of defaulting: verify marks individual claims `invalid_response` (verdict `unknown`, action `review`) while preserving valid claims; screen returns a whole-result `invalid_response` error branch with a `review` recommendation (a missing injection answer can never screen as a clean pass); find refuses to rank (`top: []`, `exists_verdict: null`). A missing, null, or non-object `answers` envelope takes the same path. Verify source answers must name a supplied evidence id or `none`; anything else yields `supporting_evidence: null` without invalidating the relation. Via [PR #8](https://github.com/jkudish/jev-mcp/pull/8) by oppih, with shared choice validation, a float-safe probability-sum tolerance, and source validation added on top.
|
|
12
|
+
- Choice answers are validated by one shared contract across tools (exact expected keys, finite [0,1] probabilities summing to one within a float-safe 0.01 tolerance, an argmax choice); `jev_classify` now reports a supplied confidence that is not a finite number in [0,1] as `null` instead of passing it through.
|
|
13
|
+
- Jev-compatible endpoints: with `JEV_API_KEY` and `JEV_API_BASE_URL` set (the full POST URL of a System One-compatible endpoint, including the `/v1/systemone` path, used verbatim with no normalization), judgments can run through any service that speaks the `{ model, state, questions }` / `{ answers, usage }` contract; `JEV_PROVIDER=compatible` forces it, and auto-detection picks it when no other provider credentials are present. Responses are validated at the envelope level: a non-object body or `answers`, malformed `usage` counts when a usage block is present, and a non-string `model` are rejected at the transport, while per-question answer validity is judged by each tool under its `invalid_response` contract so a missing or malformed answer fails closed instead of aborting the call; non-2xx error bodies are redacted so a reflecting endpoint can never echo the configured key into MCP-visible errors. Via [PR #6](https://github.com/jkudish/jev-mcp/pull/6) by Garfielk, with the response validation and key redaction hardened on top.
|
|
14
|
+
|
|
15
|
+
## 0.5.0
|
|
16
|
+
|
|
17
|
+
- New `jev_review` tool: score a proposed diff against the request before the task is called done. Four 0..2 Score rubrics (correctness, spec match, test gap, blast radius, the last two inverted in the composite) plus a safe-to-apply probability, combined into a weighted composite (0.4 / 0.3 / 0.15 / 0.15) and one `auto | review | escalate` action. Auto requires safe_to_apply and every rubric confidence at `auto_accept` (default 0.8) and the composite at `composite_floor` (default 0.7); below `review_at` escalates. Unknown confidence escalates and can never satisfy a threshold, even a zero one. Text fields capped at 50,000 characters; truncated or malformed input never returns auto. Question design adapted from [burnigtm/jev-mcp](https://github.com/burnigtm/jev-mcp) (MIT) via [PR #2](https://github.com/jkudish/jev-mcp/pull/2) by rimusz, with deliberate divergences: caps raised from 2,000 characters so auto is reachable on real diffs, thresholds parameterized instead of a hardcoded safe_to_apply escalation, strict per-answer validation under the package's invalid_response contract, and anti-injection framing (every field is evidence to evaluate, never instructions to follow).
|
|
18
|
+
- New `jev_gate` tool: the completion gate. The same patch review plus up to 16 completion claims verified against caller-supplied evidence in one request, each claim a Choice over verified / contradicted / unsupported, with claim questions instructed to use evidence only (the request, diff, and tests fields are assertions to check, never proof; all fields share one model state, so the isolation is instructional). Evidence is capped at 16 items and 200,000 characters in aggregate, rejected before any model call. The final action is the worst of the review and every claim; `reason_codes` records why (`incomplete_context`, `invalid_response`, `review_escalated`, `review_required`, `claims_contradicted`, `claims_unsupported`, `claim_confidence_low`, `claim_confidence_below_auto_accept`, `accepted`). Same credits and divergences as `jev_review`.
|
|
19
|
+
- Deliberately excluded from that PR: its `jev_coding_loop` tool, an agent loop that runs the review gate repeatedly. This package stays judgment primitives that a coding agent calls; it does not become one.
|
|
20
|
+
- Docs: README restructured around the ten tools (capability bullets, collapsible per-client install sections, a When to call which tool section), the `jev_rerank` example replaced with a live-captured semantic-ranking example that shows where candidate text comes from, and guidance added for ranking whole documents by chunking with distinct ids.
|
|
21
|
+
- New `jev_rerank` tool: one independent relevance probability per candidate in a single request, returned sorted with scores. Candidate ids preserved verbatim; any malformed answer marks the whole ranking `invalid_response` instead of sorting a missing score as zero. TypeSafe's rerank cookbook reports that on the CLERC benchmark this pattern lifted top-1 from 5% to 18% and top-10 from 38% to 62%. Bounded at 250 candidates and a 100,000-character aggregate budget.
|
|
22
|
+
- New `jev_compare` tool: overall same_fact / contradicts / different_facts relation between two passages, plus optional per-aspect judgments, each an independent Choice in the same request with aspect-specific wording for the third outcome, with distributions, confidence, an argmax guard, and an auto-versus-review decision.
|
|
23
|
+
- New `jev_extract` tool: your regex finds candidate substrings, Jev picks which candidate is each field's value, and the value returns verbatim. Regexes run in a sandboxed worker with a 1-second deadline, so pathological patterns cannot hang the server. Complete zero-match fields are omitted from the model request (`not_found`); if no field reaches the model, no API call is made, and a confident `none_of_them` is a model-judged negative. An incomplete candidate universe (matches beyond the 20-per-field cap, or matches skipped for exceeding 2,000 characters) forces every otherwise-valid positive or `none_of_them` result to `review` (malformed answers remain `invalid_response`), with explicit flags rather than silent truncation; invalid patterns and timeouts report `invalid_pattern` without failing the call. Bounded at 32 fields, a 50,000-character document, and a separate 50,000-character aggregate candidate-match budget.
|
|
24
|
+
|
|
3
25
|
## 0.4.0
|
|
4
26
|
|
|
5
27
|
- New `jev_classify` tool: assign up to 64 items to a shared catalog of up to 250 classes in one batched request (catalog sent once, one Choice per item). Per-item distribution, confidence, winner margin, and an auto-versus-review decision gated on both top probability (default 0.85) and margin (default 0.5). Caller IDs are preserved verbatim (opaque internal keys on the wire); the catalog is sent once in shared state with each item in its own question; malformed responses surface as invalid_response rather than uncertainty; batches are capped at an 8,000 item-class budget.
|
package/README.md
CHANGED
|
@@ -5,13 +5,33 @@
|
|
|
5
5
|
|
|
6
6
|
Fast, cheap, typed judgments from TypeSafe's Jev model, as MCP tools.
|
|
7
7
|
|
|
8
|
-
Give your agent
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
8
|
+
Give your agent ten judgment tools:
|
|
9
|
+
|
|
10
|
+
- `jev_verify` checks claims against evidence.
|
|
11
|
+
- `jev_screen` judges content before it enters context.
|
|
12
|
+
- `jev_find` picks the best candidate by meaning.
|
|
13
|
+
- `jev_rerank` scores and sorts every candidate.
|
|
14
|
+
- `jev_classify` batch-assigns items to classes.
|
|
15
|
+
- `jev_decide` settles bounded alternatives.
|
|
16
|
+
- `jev_compare` judges how two passages relate.
|
|
17
|
+
- `jev_extract` pulls field values with regex plus judgment.
|
|
18
|
+
- `jev_review` scores a proposed diff before the task is called done.
|
|
19
|
+
- `jev_gate` reviews a patch and verifies completion claims in one call.
|
|
20
|
+
|
|
21
|
+
Each judgment comes back typed: probabilities, and for most tools a confidence score, in roughly 150 to 500 ms, for a fraction of a cent. The cheap mechanical checks agents otherwise skip, because a frontier model is too slow to run on every page, claim, or candidate list.
|
|
22
|
+
|
|
23
|
+
What you can use it for (the use cases are endless; these are just examples):
|
|
24
|
+
|
|
25
|
+
- Fact-check a report, PR description, or agent brief against the sources it cites, claim by claim.
|
|
26
|
+
- Screen a fetched page for injected instructions before it enters context, and skip pages with nothing to say.
|
|
27
|
+
- Find which document, file, or note answers a question, across hundreds of candidates, with no embeddings and no index to maintain.
|
|
28
|
+
- Rerank retrieval results, triage near-duplicates, or order a feed by relevance.
|
|
29
|
+
- Route support messages, label issues, or sort an inbox against your own label set, in batches.
|
|
30
|
+
- Choose between a handful of options with evidence and priorities in view, with an explicit ask-the-user escape hatch when it cannot decide.
|
|
31
|
+
- Reconcile a changelog against its docs, a summary against its source, or catch two pages that disagree about a price or a date.
|
|
32
|
+
- Pull prices, dates, versions, and IDs out of a page or document as verbatim strings the model found but never wrote.
|
|
33
|
+
- Score a proposed diff for correctness, spec match, test gap, and blast radius before your agent declares the task done.
|
|
34
|
+
- Gate a merge or a ship on completion claims: the patch review and every "tests pass" claim checked against the evidence actually supplied.
|
|
15
35
|
|
|
16
36
|
This is early software. Expect rough edges. Issues and pull requests are welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
17
37
|
|
|
@@ -38,19 +58,26 @@ From npm:
|
|
|
38
58
|
npx -y @jkudish/jev-mcp
|
|
39
59
|
```
|
|
40
60
|
|
|
41
|
-
|
|
61
|
+
<details>
|
|
62
|
+
<summary>Amp</summary>
|
|
42
63
|
|
|
43
64
|
```bash
|
|
44
65
|
amp mcp add jev -- npx -y @jkudish/jev-mcp
|
|
45
66
|
```
|
|
46
67
|
|
|
47
|
-
|
|
68
|
+
</details>
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary>Claude Code</summary>
|
|
48
72
|
|
|
49
73
|
```bash
|
|
50
74
|
claude mcp add jev -- npx -y @jkudish/jev-mcp
|
|
51
75
|
```
|
|
52
76
|
|
|
53
|
-
|
|
77
|
+
</details>
|
|
78
|
+
|
|
79
|
+
<details>
|
|
80
|
+
<summary>Codex (<code>~/.codex/config.toml</code>)</summary>
|
|
54
81
|
|
|
55
82
|
```toml
|
|
56
83
|
[mcp_servers.jev]
|
|
@@ -58,7 +85,10 @@ command = "npx"
|
|
|
58
85
|
args = ["-y", "@jkudish/jev-mcp"]
|
|
59
86
|
```
|
|
60
87
|
|
|
61
|
-
|
|
88
|
+
</details>
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary>OpenCode (<code>opencode.json</code>)</summary>
|
|
62
92
|
|
|
63
93
|
```json
|
|
64
94
|
{
|
|
@@ -72,7 +102,10 @@ args = ["-y", "@jkudish/jev-mcp"]
|
|
|
72
102
|
}
|
|
73
103
|
```
|
|
74
104
|
|
|
75
|
-
|
|
105
|
+
</details>
|
|
106
|
+
|
|
107
|
+
<details>
|
|
108
|
+
<summary>Any other MCP client</summary>
|
|
76
109
|
|
|
77
110
|
```json
|
|
78
111
|
{
|
|
@@ -86,6 +119,8 @@ args = ["-y", "@jkudish/jev-mcp"]
|
|
|
86
119
|
}
|
|
87
120
|
```
|
|
88
121
|
|
|
122
|
+
</details>
|
|
123
|
+
|
|
89
124
|
Some MCP clients filter the environment before spawning servers, which silently drops `TYPESAFE_API_KEY`. If the server reports a missing key, pass it explicitly as shown above.
|
|
90
125
|
|
|
91
126
|
## The tools
|
|
@@ -118,7 +153,20 @@ Check each claim in a report, PR description, or agent brief against the sources
|
|
|
118
153
|
}
|
|
119
154
|
```
|
|
120
155
|
|
|
121
|
-
|
|
156
|
+
Malformed or missing relation answers fail closed per claim (other valid claims are preserved). A missing, null, or non-object `answers` envelope invalidates every claim:
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
// invalid result entry; tool/model/provider/auto_accept/summary/results/usage remain
|
|
160
|
+
{
|
|
161
|
+
"id": "claim0", "claim": "The claim being checked",
|
|
162
|
+
"verdict": "unknown", "probabilities": null, "confidence": null,
|
|
163
|
+
"status": "invalid_response", "action": "review", "supporting_evidence": null
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
- Relation choices must belong to the requested set and be a maximum-probability option. Distributions must contain exactly all relation keys, with finite probabilities in `[0,1]` summing to 1 within `0.01`.
|
|
168
|
+
- Missing or null confidence stays `null` and requires `review`, even with `auto_accept: 0`. Non-number, non-finite, or out-of-range confidence invalidates the claim and is returned as `null`; numeric zero is valid.
|
|
169
|
+
- With multiple evidence items, each claim also gets the id of the evidence it rests on. These source answers are optional auxiliary information; missing sources yield `supporting_evidence: null` without invalidating a valid relation. A present source must be a well-formed choice over the evidence ids plus `none`; anything else yields `null`.
|
|
122
170
|
- `auto_accept` (default `0.8`) is the confidence at or above which a verdict stands. Lower-confidence verdicts come back flagged `review`.
|
|
123
171
|
- For quote-level citation checks, match quotes against the source in code first and send only the surviving claims. See the [citation-check cookbook](https://docs.typesafe.ai/cookbooks/citation_check).
|
|
124
172
|
|
|
@@ -142,6 +190,19 @@ Judge fetched or pasted text before an agent reads it. One call returns the prob
|
|
|
142
190
|
}
|
|
143
191
|
```
|
|
144
192
|
|
|
193
|
+
Missing or malformed required answers return an error branch:
|
|
194
|
+
|
|
195
|
+
```jsonc
|
|
196
|
+
// abridged; tool/model/provider/thresholds/usage remain
|
|
197
|
+
{
|
|
198
|
+
"status": "invalid_response",
|
|
199
|
+
"probabilities": { "injection": null, "substance": 0.9, "relevance": null },
|
|
200
|
+
"recommendation": { "action": "review", "reason": "missing or malformed answers; cannot screen safely" }
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
All requested probabilities must be finite numbers in `[0,1]`; zero is valid. Invalid or missing probabilities become `null`, while valid values are retained. Relevance is required only when a non-empty purpose is supplied; otherwise it is `null`. A missing, null, or non-object `answers` envelope also takes this error branch.
|
|
205
|
+
|
|
145
206
|
- The recommendation is advisory: `pass`, `review`, `block`, or `skip`. The server never blocks on its own; enforcement stays with the calling agent.
|
|
146
207
|
- Low substance or relevance yields `skip`: the page is not worth reading.
|
|
147
208
|
- `block_at` (default `0.75`) and `review_at` (default `0.25`) are thresholds on the injection probability. Both are parameters.
|
|
@@ -176,7 +237,19 @@ Rank candidates against a plain-language query. No embeddings, no index to maint
|
|
|
176
237
|
}
|
|
177
238
|
```
|
|
178
239
|
|
|
179
|
-
|
|
240
|
+
Missing or malformed `exists` or `best` answers return an error branch:
|
|
241
|
+
|
|
242
|
+
```jsonc
|
|
243
|
+
// abridged; tool/model/provider/query/usage remain
|
|
244
|
+
{
|
|
245
|
+
"status": "invalid_response", "exists": null, "exists_verdict": null, "top": [],
|
|
246
|
+
"reason": "missing or malformed best or exists answer; cannot rank safely"
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`exists` must be a finite number in `[0,1]`; zero validly means `absent`. On failure, a valid `exists` value is retained; an invalid or missing value becomes `null`. The best distribution must contain exactly all candidate ids, finite probabilities in `[0,1]` summing to 1 within `0.01`, and a string choice tied for the maximum probability. Missing, null, or non-object `answers` also returns this error branch. Protocol failure is reported only in `status`; `exists_verdict` is `null` on failure.
|
|
251
|
+
|
|
252
|
+
- On successful responses, ranking always returns a winner, because Choice probabilities sum to 1. A top hit can masquerade as an answer when none is present; the exists check catches that. `exists_verdict` is `answered`, `partial`, or `absent`.
|
|
180
253
|
- Up to 250 candidates per call. Candidate texts are truncated at 2,000 characters.
|
|
181
254
|
- Pattern from the [semantic-find cookbook](https://docs.typesafe.ai/cookbooks/semantic_find).
|
|
182
255
|
|
|
@@ -213,17 +286,333 @@ Assign each item to one class from a shared catalog, in one batched request: the
|
|
|
213
286
|
- Include a `manual_review` class in your catalog if you want an explicit escape hatch; the tool never invents one.
|
|
214
287
|
- Class descriptions carry the decision. Strong ones state a precise definition, what belongs, what does not, precedence over overlapping classes, and a short example.
|
|
215
288
|
- Up to 250 classes and 64 items per call, with an 8,000 item-class budget per batch (split larger waves into multiple calls); item text is truncated at 2,000 characters.
|
|
216
|
-
- A malformed or incomplete model response is reported as `status: invalid_response` on that item, never as model uncertainty.
|
|
289
|
+
- A malformed or incomplete model response is reported as `status: invalid_response` on that item, never as model uncertainty. The chosen class must have the maximum probability (ties and differences within `1e-9` are accepted); otherwise that item returns `invalid_response`.
|
|
290
|
+
|
|
291
|
+
### jev_decide
|
|
292
|
+
|
|
293
|
+
One bounded decision, 2-6 candidates, evidence, and explicit priorities. Jev returns a Choice distribution over the candidates plus escape hatches, and a per-candidate per-requirement check, in one request.
|
|
294
|
+
|
|
295
|
+
```jsonc
|
|
296
|
+
// arguments
|
|
297
|
+
{
|
|
298
|
+
"decision": "Choose the report status update channel.",
|
|
299
|
+
"evidence": "Polling updates within 30 seconds. Managed push updates within one second but adds a paid vendor.",
|
|
300
|
+
"priorities": "The user accepts 30 seconds and prioritizes no new paid services.",
|
|
301
|
+
"candidates": [
|
|
302
|
+
{ "id": "poll", "description": "Poll the existing authenticated endpoint." },
|
|
303
|
+
{ "id": "push", "description": "Add the managed push service." }
|
|
304
|
+
],
|
|
305
|
+
"requirements": ["No new paid service is needed."]
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
```jsonc
|
|
310
|
+
// live result, abridged
|
|
311
|
+
{
|
|
312
|
+
"recommendation": { "selected": "poll", "escaped": false, "confidence": 1,
|
|
313
|
+
"probabilities": { "poll": 1, "push": 0, "ask_user": 0 } },
|
|
314
|
+
"checks": [ { "candidate": "poll", "requirement": 0, "answer": "supported" },
|
|
315
|
+
{ "candidate": "push", "requirement": 0, "answer": "contradicted" } ]
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
- Escape hatches (`ask_user`, `investigate`, `none`) let the model decline to rank when a preference or fact is missing; `escaped: true` in the result marks it. Disable with `escape_hatches: false` for closed-world choices.
|
|
320
|
+
- Requirement checks run as independent questions in the same request and may disagree with the recommendation; a contradiction on the recommended candidate surfaces as a warning.
|
|
321
|
+
- One call per unchanged decision. Repeat only with materially new evidence or criteria.
|
|
322
|
+
|
|
323
|
+
<sub>Pattern credit: [thesammykins/jev_ampcode](https://github.com/thesammykins/jev_ampcode).</sub>
|
|
324
|
+
|
|
325
|
+
### jev_rerank
|
|
326
|
+
|
|
327
|
+
Score every candidate's relevance to a query and get them back sorted. You bring the candidates (file contents, database rows, search hits); Jev scores and sorts what you hand it. Unlike `jev_find`, which picks one best answer, rerank gives each candidate its own relevance probability, so the whole ordering survives. TypeSafe's rerank cookbook reports that on the CLERC benchmark this pattern lifted top-1 from 5% to 18% and top-10 from 38% to 62%.
|
|
328
|
+
|
|
329
|
+
```jsonc
|
|
330
|
+
// arguments
|
|
331
|
+
{
|
|
332
|
+
"query": "why did our bandwidth charges triple",
|
|
333
|
+
"candidates": [
|
|
334
|
+
{
|
|
335
|
+
"id": "infra/main.tf",
|
|
336
|
+
"text": "resource \"aws_instance\" \"api\" {\n count = 3 # always-on\n instance_type = \"m5.large\"\n}"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"id": "src/cache.ts",
|
|
340
|
+
"text": "// CDN cache control\nexport const CDN_TTL_SECONDS = 60; // was 86400 until the perf sprint"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"id": "docs/runbook.md",
|
|
344
|
+
"text": "# On-call runbook\n\nEscalation contacts and the weekly rotation schedule."
|
|
345
|
+
}
|
|
346
|
+
]
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```jsonc
|
|
351
|
+
// live result, abridged
|
|
352
|
+
{
|
|
353
|
+
"ranked": [
|
|
354
|
+
{ "rank": 1, "id": "src/cache.ts", "relevance": 0.74 },
|
|
355
|
+
{ "rank": 2, "id": "infra/main.tf", "relevance": 0.23 },
|
|
356
|
+
{ "rank": 3, "id": "docs/runbook.md", "relevance": 0.03 }
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Each candidate is a file: `id` is any handle you choose, echoed back verbatim, and `text` is the file's contents (truncated at 2,000 characters). No candidate contains the words bandwidth or triple. A shorter CDN TTL means more origin fetches, so `src/cache.ts` ranks first on meaning alone; the always-on VMs are cloud spend too, just not bandwidth.
|
|
362
|
+
|
|
363
|
+
- One relevance probability per candidate, all in a single request; cost scales with the number of candidates, not with candidate-pairs.
|
|
364
|
+
- Candidate ids are preserved verbatim. If any answer comes back malformed, the whole ranking is reported `invalid_response` rather than sorting a missing score as a confident zero.
|
|
365
|
+
- Up to 250 candidates and a 100,000-character aggregate budget; split larger batches.
|
|
366
|
+
- Ranking whole documents? Chunk them into ~2,000-character candidates with distinct ids (`report.md#c1`, `report.md#c2`) and merge per document by its best chunk's score.
|
|
367
|
+
- Use `jev_find` when you want one best answer plus an existence check; use `jev_rerank` when the ordering itself is the deliverable. See the [rerank cookbook](https://docs.typesafe.ai/cookbooks/rerank_typesafe).
|
|
368
|
+
|
|
369
|
+
### jev_compare
|
|
370
|
+
|
|
371
|
+
Judge how two passages relate: `same_fact`, `contradicts`, or `different_facts`, with the full probability distribution, confidence, and an auto-versus-review decision. Supply optional aspects (price, launch date, method) and each gets its own independent judgment in the same request.
|
|
372
|
+
|
|
373
|
+
```jsonc
|
|
374
|
+
// arguments
|
|
375
|
+
{
|
|
376
|
+
"passage_a": "The Pro plan costs $29 per month and includes unlimited builds.",
|
|
377
|
+
"passage_b": "The Pro plan is priced at $59 per month. All plans include unlimited builds.",
|
|
378
|
+
"aspects": ["price", "build limits"]
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
```jsonc
|
|
383
|
+
// live result, abridged
|
|
384
|
+
{
|
|
385
|
+
"overall": { "relation": "contradicts", "confidence": 1, "decision": "auto" },
|
|
386
|
+
"aspects": [
|
|
387
|
+
{ "aspect": "price", "relation": "contradicts", "decision": "auto" },
|
|
388
|
+
{ "aspect": "build limits", "relation": "same_fact", "decision": "auto" }
|
|
389
|
+
]
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
- Per-aspect judgments are independent and may disagree with the overall relation; that disagreement is signal, not noise.
|
|
394
|
+
- Each passage is capped at 20,000 characters; requests above that are rejected up front.
|
|
395
|
+
- At aspect granularity, `different_facts` explicitly means the passages do not both make a comparable assertion about the aspect: at least one does not address it, or their mentions do not overlap.
|
|
396
|
+
- The request supplies no evidence beyond the two passages, so a `same_fact` verdict means they agree with each other, not that they are true.
|
|
397
|
+
- Use for source reconciliation, changelog-versus-code drift, or checking that a summary matches its source.
|
|
398
|
+
|
|
399
|
+
### jev_extract
|
|
400
|
+
|
|
401
|
+
Pull structured fields out of a document with your regex and Jev's judgment. Your regex finds candidate substrings in code, Jev picks which candidate is the field's real value, and the value comes back verbatim, exactly as it appears in the document, never model-generated.
|
|
402
|
+
|
|
403
|
+
```jsonc
|
|
404
|
+
// arguments
|
|
405
|
+
{
|
|
406
|
+
"document": "Starter is $9/mo. Pro is $29/mo. Enterprise: contact sales. Version 3.2.1 released 2024-06-01. The early-bird launch price for Pro was $19/mo.",
|
|
407
|
+
"fields": [
|
|
408
|
+
{ "id": "price_pro", "pattern": "\\$\\d+", "description": "The current monthly price of the Pro plan in US dollars" },
|
|
409
|
+
{ "id": "version", "pattern": "\\d+\\.\\d+\\.\\d+", "description": "The release version number of the software" }
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
```jsonc
|
|
415
|
+
// live result, abridged
|
|
416
|
+
{
|
|
417
|
+
"results": [
|
|
418
|
+
{ "id": "price_pro", "value": "$29", "status": "auto", "candidates_considered": 3,
|
|
419
|
+
"candidates_truncated": false, "matches_skipped_too_long": 0 },
|
|
420
|
+
{ "id": "version", "value": "3.2.1", "status": "auto", "candidates_considered": 1,
|
|
421
|
+
"candidates_truncated": false, "matches_skipped_too_long": 0 }
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
- A field whose regex matches nothing comes back `not_found` with reason `no_regex_matches` and never reaches the model: no hallucinated value. In a call where every field is a zero-match, no API call is made at all. Jev can also pick `none_of_them` when every regex match is wrong for the field; that `not_found` is model-judged and gated on top probability and winner margin like any pick.
|
|
427
|
+
- Values are verbatim document substrings, exactly as the regex matched them. The model picks among matches; it never writes a value.
|
|
428
|
+
- Ambiguous picks come back flagged `review` with the value still attached; treat a `review` value as provisional, not extracted. If the regex found more matches than the cap allows, or skipped matches longer than 2,000 characters, the field can never be `auto` and a `none_of_them` pick can never be a definite `not_found`: it returns `review` with reason `candidate_limit` and `candidates_truncated` or `matches_skipped_too_long` set, because the best value may be among the unsent matches. When every match is over 2,000 characters and none is eligible at all, the reason is `matches_too_long` instead. A malformed model answer is still `invalid_response`, not a semantic outcome.
|
|
429
|
+
- Invalid patterns and regexes that time out (they run in a sandboxed worker with a 1-second deadline, so a pathological pattern cannot hang the server) return `invalid_pattern` with the error instead of failing the whole call.
|
|
430
|
+
- Up to 32 fields per call and 20 candidate matches per field, judged in one request. The document is capped at 50,000 characters, and the candidate match text at 50,000 characters in aggregate.
|
|
431
|
+
|
|
432
|
+
### jev_review
|
|
433
|
+
|
|
434
|
+
Score a proposed diff against the request before the task is called done. Jev answers four rubric questions, correctness, spec match, test gap, and blast radius, each 0..2, plus one safe-to-apply probability; the server combines them into a weighted composite and one action: `auto`, `review`, or `escalate`. It judges what you hand it. It never runs tests and never applies the patch.
|
|
435
|
+
|
|
436
|
+
```jsonc
|
|
437
|
+
// arguments
|
|
438
|
+
{
|
|
439
|
+
"request": "Our CLI reads a JSON config from stdin. It crashed on empty input. Make it tolerate an empty document and any whitespace-only document, returning our zero-value config instead.",
|
|
440
|
+
"diff": "--- a/src/parse.ts\n+++ b/src/parse.ts\n@@ def parse(stdin) @@\n- return JSON.parse(stdin);\n+ const trimmed = stdin.trim();\n+ if (trimmed === \"\") return zeroConfig();\n+ return JSON.parse(trimmed);",
|
|
441
|
+
"tests": "node --test: 2 passed, 1 failing (parse: invalid JSON still rejects)"
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
```jsonc
|
|
446
|
+
// live result, abridged
|
|
447
|
+
{
|
|
448
|
+
"action": "escalate",
|
|
449
|
+
"composite": 0.756,
|
|
450
|
+
"safe_to_apply": 0.24,
|
|
451
|
+
"scores": {
|
|
452
|
+
"correctness": { "score": 1.51, "confidence": 0.27, "probabilities": null },
|
|
453
|
+
"spec_match": { "score": 1.65, "confidence": 0.47, "probabilities": null },
|
|
454
|
+
"test_gap": { "score": 0.80, "confidence": 0.14, "probabilities": null },
|
|
455
|
+
"blast_radius":{ "score": 0.45, "confidence": 0.32, "probabilities": null }
|
|
456
|
+
},
|
|
457
|
+
"reason_codes": ["confidence_below_review", "safe_to_apply_below_review"],
|
|
458
|
+
"limiting_rubrics": ["test_gap"],
|
|
459
|
+
"weights": { "correctness": 0.4, "spec_match": 0.3, "test_gap": 0.15, "blast_radius": 0.15 },
|
|
460
|
+
"thresholds": { "auto_accept": 0.8, "review_at": 0.5, "composite_floor": 0.7 },
|
|
461
|
+
"truncated": false,
|
|
462
|
+
"usage": { "input_tokens": 855, "output_tokens": 79 }
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Here the composite clears the floor, but the failing test drags `safe_to_apply` to 0.24 and rubric confidences sit under `review_at`, so the patch escalates instead of sailing through on its decent scores.
|
|
467
|
+
|
|
468
|
+
- Rubric scores run 0..2. Higher is better for `correctness` and `spec_match`; higher is worse for `test_gap` and `blast_radius`, and the composite inverts those two before weighting, so a composite of 1.0 means favorable on every rubric.
|
|
469
|
+
- A score answer may carry its full probability distribution over the three options; when the provider reports one, it is validated (exact keys, probabilities summing to one, expected value within a small tolerance of the reported score) and returned in `scores.*.probabilities`. Absent means "not reported" and stays `null`; a distribution that is present but malformed or contradictory marks that rubric `invalid_response`.
|
|
470
|
+
- `reason_codes` collects why the review decided as it did: `invalid_response`, `unknown_confidence`, `confidence_below_review`, `safe_to_apply_below_review`, `confidence_below_auto_accept`, `safe_to_apply_below_auto_accept`, `composite_below_floor`, `incomplete_context`, `accepted`. `limiting_rubrics` names the rubric(s) that bound the decision, ties included: the null-confidence rubrics when confidence is unknown, every rubric tied at the minimum confidence when a confidence threshold blocks, and the least favorable rubrics when the composite floor blocks.
|
|
471
|
+
- `auto` requires `safe_to_apply` and every rubric confidence at `auto_accept` and the composite at `composite_floor`. `safe_to_apply` or any rubric confidence below `review_at`, or unknown, escalates; a composite below `composite_floor` returns `review`, not `escalate`. Unknown confidence counts as escalate, never as a value that can satisfy a threshold.
|
|
472
|
+
- `request` frames the review; it is not proof of anything. Put real output in `tests`. Every field is treated as evidence to evaluate, never instructions to follow.
|
|
473
|
+
- Each text field is capped at 50,000 characters. Truncated input sets `truncated: true` and can never return `auto`; a malformed answer is `invalid_response`, not a semantic outcome.
|
|
474
|
+
|
|
475
|
+
<sub>Adapted from [burnigtm/jev-mcp](https://github.com/burnigtm/jev-mcp) (MIT), via [PR #2](https://github.com/jkudish/jev-mcp/pull/2) by rimusz.</sub>
|
|
476
|
+
|
|
477
|
+
### jev_gate
|
|
478
|
+
|
|
479
|
+
The completion gate: the same patch review as `jev_review`, plus your completion claims verified against evidence you supply, in one call. Auto only when the review is accepted and every claim is verified at or above `auto_accept`; a confidently contradicted claim escalates. The request and the claims are assertions to check, never proof.
|
|
480
|
+
|
|
481
|
+
```jsonc
|
|
482
|
+
// arguments
|
|
483
|
+
{
|
|
484
|
+
"request": "Our CLI reads a JSON config from stdin. It crashed on empty input. Make it tolerate an empty document and any whitespace-only document, returning our zero-value config instead.",
|
|
485
|
+
"diff": "--- a/src/parse.ts\n+++ b/src/parse.ts\n@@ def parse(stdin) @@\n- return JSON.parse(stdin);\n+ const trimmed = stdin.trim();\n+ if (trimmed === \"\") return zeroConfig();\n+ return JSON.parse(trimmed);",
|
|
486
|
+
"claims": [
|
|
487
|
+
"The empty-input parser test passed.",
|
|
488
|
+
"Whitespace-only input is also handled.",
|
|
489
|
+
"The full test suite passes with no failures."
|
|
490
|
+
],
|
|
491
|
+
"evidence": [
|
|
492
|
+
{ "id": "test-log", "text": "node --test output: 2 passed, 1 failing (parse: invalid JSON still rejects)." },
|
|
493
|
+
{ "id": "diff", "text": "parse.ts: trimmed input; empty string returns zeroConfig(); JSON.parse on the trimmed text otherwise." }
|
|
494
|
+
],
|
|
495
|
+
"tests": "node --test: 2 passed, 1 failing (parse: invalid JSON still rejects)"
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
```jsonc
|
|
500
|
+
// live result, abridged
|
|
501
|
+
{
|
|
502
|
+
"action": "escalate",
|
|
503
|
+
"reason_codes": ["review_escalated", "claims_contradicted"],
|
|
504
|
+
"review": { "action": "escalate", "composite": 0.816, "safe_to_apply": 0.19 },
|
|
505
|
+
"verification": {
|
|
506
|
+
"action": "escalate",
|
|
507
|
+
"summary": { "verified": 2, "contradicted": 1, "unsupported": 0, "needs_review": 1 },
|
|
508
|
+
"results": [
|
|
509
|
+
{ "claim": "The empty-input parser test passed.",
|
|
510
|
+
"verdict": "verified", "confidence": 1, "action": "auto" },
|
|
511
|
+
// second claim likewise verified at confidence 1
|
|
512
|
+
{ "claim": "The full test suite passes with no failures.",
|
|
513
|
+
"verdict": "contradicted", "confidence": 1, "action": "escalate" }
|
|
514
|
+
]
|
|
515
|
+
},
|
|
516
|
+
"truncated": false,
|
|
517
|
+
"usage": { "input_tokens": 1559, "output_tokens": 201 }
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
The two true claims verify at full confidence, and the one that matters, "the full test suite passes," is contradicted by the test log at full confidence: exactly the claim a coding agent is most tempted to hand-wave.
|
|
522
|
+
|
|
523
|
+
- Claim questions instruct Jev to use `evidence` only, not world knowledge, and not the request, diff, or tests fields; if a claim needs a diff excerpt or a test log as support, supply it in `evidence`. All fields share one model state, so this is instruction-level isolation, not a hard boundary. Every field is evidence to evaluate, never instructions to follow.
|
|
524
|
+
- `reason_codes` collects why the gate decided as it did: `incomplete_context`, `invalid_response`, `review_escalated`, `review_required`, plus the review half's specific codes (`unknown_confidence`, `confidence_below_review`, `safe_to_apply_below_review`, `confidence_below_auto_accept`, `safe_to_apply_below_auto_accept`, `composite_below_floor`), `claims_contradicted`, `claims_unsupported`, `claim_confidence_low`, `claim_confidence_below_auto_accept`, `accepted`. The embedded `review` object carries the same `reason_codes` and `limiting_rubrics` a standalone `jev_review` returns.
|
|
525
|
+
- Up to 16 claims and 16 evidence items per call. Text fields are capped at 50,000 characters each, claims at 2,000, and evidence at 200,000 characters in aggregate; oversized evidence is rejected before any model call. Malformed answers surface as `invalid_response` and the gate never returns `auto` on one.
|
|
526
|
+
- Use `jev_verify` for claims without a patch review, and `jev_review` for a patch without claims.
|
|
527
|
+
|
|
528
|
+
<sub>Adapted from [burnigtm/jev-mcp](https://github.com/burnigtm/jev-mcp) (MIT), via [PR #2](https://github.com/jkudish/jev-mcp/pull/2) by rimusz.</sub>
|
|
529
|
+
|
|
530
|
+
## When to call which tool
|
|
531
|
+
|
|
532
|
+
- `jev_verify`: one or more claims against evidence you already have.
|
|
533
|
+
- `jev_screen`: fetched or pasted content, before it enters context.
|
|
534
|
+
- `jev_find`: pick the single best candidate from up to 250.
|
|
535
|
+
- `jev_rerank`: score and sort the whole list.
|
|
536
|
+
- `jev_classify`: label many items against your own catalog, in batches.
|
|
537
|
+
- `jev_decide`: choose between a handful of options with priorities in view.
|
|
538
|
+
- `jev_compare`: how two passages relate, overall or per aspect.
|
|
539
|
+
- `jev_extract`: pull field values a regex can find, verbatim.
|
|
540
|
+
- `jev_review`: score a proposed diff before calling the task done.
|
|
541
|
+
- `jev_gate`: that same review plus completion claims checked against evidence.
|
|
542
|
+
|
|
543
|
+
## Combining the tools: task routing
|
|
544
|
+
|
|
545
|
+
Routing is a good example of combining tools. Before any work starts, you want to know what kind of task this is and which of your workflows should run it. A `jev_classify` call answers the first question, a `jev_decide` call the second.
|
|
546
|
+
|
|
547
|
+
Everything below is an example, not a feature: the package ships the two calls, and the classes, routes, and rules are yours to define.
|
|
548
|
+
|
|
549
|
+
First, classify the task on the axes your routing cares about. Risk is a useful one:
|
|
550
|
+
|
|
551
|
+
```jsonc
|
|
552
|
+
// arguments
|
|
553
|
+
{
|
|
554
|
+
"purpose": "Decide how to handle an incoming task",
|
|
555
|
+
"context": "The workspace has a code checkout, a database, and a deploy pipeline.",
|
|
556
|
+
"items": [
|
|
557
|
+
{ "id": "task", "text": "Add a dark mode toggle to the settings page." }
|
|
558
|
+
],
|
|
559
|
+
"classes": [
|
|
560
|
+
{ "id": "read_only", "description": "Answers without changing anything: reading files, listing records, summarizing." },
|
|
561
|
+
{ "id": "reversible", "description": "Changes state but can be undone: local edits, draft records, a staging deploy." },
|
|
562
|
+
{ "id": "destructive", "description": "Cannot be undone automatically: deleting records, force-pushes, production deploys, payments." }
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
```jsonc
|
|
568
|
+
// live result, abridged
|
|
569
|
+
{
|
|
570
|
+
"results": [
|
|
571
|
+
{ "id": "task", "classification": "reversible", "margin": 0.94, "confidence": 0.97, "decision": "auto" }
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Then decide the route, feeding that answer in as evidence. The `requirements` field is where your policy lives; each one is checked per candidate in the same call:
|
|
577
|
+
|
|
578
|
+
```jsonc
|
|
579
|
+
// arguments
|
|
580
|
+
{
|
|
581
|
+
"decision": "Which workflow should run this task?",
|
|
582
|
+
"evidence": "jev_classify labeled the task reversible (confidence 0.97): it edits the checkout but touches no production system. Available workflows: answer, implement, escalate.",
|
|
583
|
+
"priorities": "Automated runs must stay reversible; destructive tasks always escalate.",
|
|
584
|
+
"candidates": [
|
|
585
|
+
{ "id": "answer", "description": "Look things up and reply. No writes." },
|
|
586
|
+
{ "id": "implement", "description": "Branch, edit, run tests, open a PR." },
|
|
587
|
+
{ "id": "escalate", "description": "Hand the task to a person." }
|
|
588
|
+
],
|
|
589
|
+
"requirements": ["Never runs an action the classification called destructive."]
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
```jsonc
|
|
594
|
+
// live result, abridged
|
|
595
|
+
{
|
|
596
|
+
"recommendation": { "selected": "implement", "escaped": false, "confidence": 0.93,
|
|
597
|
+
"probabilities": { "implement": 0.93, "escalate": 0.05, "answer": 0.02, "ask_user": 0 } },
|
|
598
|
+
"checks": [ { "candidate": "implement", "requirement": 0, "answer": "supported" } ]
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
- If either call comes back low-confidence or escaped, route to `escalate` (or ask a person) rather than guessing. The escape hatches exist for exactly that.
|
|
603
|
+
- One classify call and one decide call per task. Repeating them on the same inputs buys nothing.
|
|
604
|
+
|
|
605
|
+
<sub>Pattern credit: [@Garfielk](https://github.com/Garfielk), from the routing discussion in [#5](https://github.com/jkudish/jev-mcp/issues/5).</sub>
|
|
217
606
|
|
|
218
607
|
## How the answers work
|
|
219
608
|
|
|
220
|
-
Jev is TypeSafe's System One model: it returns typed answers with calibrated probability distributions, not generated text. A verify call is a Choice over supports / contradicts / says_nothing, so you see the whole distribution, not one label. A screen call is a set of yes/no probabilities. A find call is a Choice over your candidate ids plus an existence check. Code maps the answers to verdicts and actions; policy stays with you.
|
|
609
|
+
Jev is TypeSafe's System One model: it returns typed answers with calibrated probability distributions, not generated text. A verify call is a Choice over supports / contradicts / says_nothing, so you see the whole distribution, not one label. A screen call is a set of yes/no probabilities. A find call is a Choice over your candidate ids plus an existence check. A rerank call is one yes/no relevance question per candidate. A compare call is a Choice over three relations, repeated independently per aspect. An extract call is a Choice over the candidates your regex already found, so the model picks a value but never writes one. A review call is four Score rubrics plus one safe-to-apply probability; a gate adds one Choice per completion claim, judged from evidence only. Code maps the answers to verdicts and actions; policy stays with you.
|
|
221
610
|
|
|
222
611
|
## Limits and tuning
|
|
223
612
|
|
|
224
613
|
- Thresholds (`auto_accept`, `block_at`, `review_at`, exists cutoffs) are starting points from the TypeSafe cookbooks. Tune them against your own data before you enforce them. See [how TypeSafe reports confidence](https://docs.typesafe.ai/confidence.md).
|
|
225
614
|
- Jev is calibrated, not infallible. Typed output guarantees the interface, not the truth. Keep policy in code and escalate low-confidence results to a person or a bigger model.
|
|
226
|
-
- Every result includes token usage, so you can see what each judgment costs.
|
|
615
|
+
- Every result that calls the model includes token usage, so you can see what each judgment costs. A `jev_extract` call where no field reaches the model reports `usage: null`.
|
|
227
616
|
|
|
228
617
|
## Configuration
|
|
229
618
|
|
|
@@ -233,9 +622,18 @@ Jev is TypeSafe's System One model: it returns typed answers with calibrated pro
|
|
|
233
622
|
| `OPENROUTER_API_KEY` | none | OpenRouter `sk-or-` key; used when `TYPESAFE_API_KEY` is absent. |
|
|
234
623
|
| `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` | none | Cloudflare Workers AI; used when no other provider key is present. `JEV_CLOUDFLARE_API_TOKEN` is honored first for separate credentials. |
|
|
235
624
|
| `AI_GATEWAY_API_KEY` | none | Vercel AI Gateway; used when no other provider key is present. |
|
|
236
|
-
| `JEV_PROVIDER` | `auto` | Force `typesafe`, `openrouter`, `cloudflare`, or `
|
|
625
|
+
| `JEV_PROVIDER` | `auto` | Force `typesafe`, `openrouter`, `cloudflare`, `vercel`, or `compatible` instead of auto-detection. |
|
|
237
626
|
| `JEV_MCP_MODEL` | `jev-latest` | Pin a Jev version, e.g. `jev-1.12`, or `typesafe/jev-1.13` on OpenRouter. |
|
|
238
627
|
| `TYPESAFE_BASE_URL` | none | Custom direct endpoint (origin only; the SDK appends its route). |
|
|
628
|
+
| `JEV_API_BASE_URL` + `JEV_API_KEY` | none | Jev-compatible System One endpoint and Bearer token; use with `JEV_PROVIDER=compatible`. `JEV_API_BASE_URL` is the full POST URL including the `/v1/systemone` path. |
|
|
629
|
+
| `JEV_MCP_REQUEST_TIMEOUT_MS` | `60000` | Whole-request deadline in milliseconds, covering every attempt, on the fetch-based transports. |
|
|
630
|
+
| `JEV_MCP_MAX_ATTEMPTS` | `3` | Total attempts per request (clamped 1..6) on the fetch-based transports; retries happen only on 408, 409, 429, and 500 through 599. |
|
|
631
|
+
| `JEV_OPENROUTER_BASE_URL` | `https://openrouter.ai/api` | Override the OpenRouter API root (the `/alpha/decisions` path is appended; a trailing slash is tolerated). |
|
|
632
|
+
| `JEV_CLOUDFLARE_BASE_URL` | `https://api.cloudflare.com/client/v4` | Override the Cloudflare API root (`/accounts/<id>/ai/run` is appended; a trailing slash is tolerated). |
|
|
633
|
+
|
|
634
|
+
### Transport resilience
|
|
635
|
+
|
|
636
|
+
The fetch-based transports (OpenRouter, Cloudflare, and the Jev-compatible endpoint) retry only on the standard not-processed status set (408, 409, 429, and 500 through 599), with jittered exponential backoff, at most `JEV_MCP_MAX_ATTEMPTS` total attempts, all inside one `JEV_MCP_REQUEST_TIMEOUT_MS` deadline. A status cannot prove the request was not processed, but that allowlist is the conservative retry trigger; ambiguous network-level failures (connection reset, TLS errors) are never retried, because without an idempotency key a re-send can double-process a paid call. Everything else fails immediately: caller cancellations, deadline expiry, non-retryable statuses, unparseable bodies, and responses over 1,000,000 bytes, a ceiling enforced while the body streams rather than after buffering. A cancelled MCP call aborts the in-flight HTTP request, cuts any backoff sleep short, and is never re-sent. Error bodies on those transports are redacted, so a reflecting endpoint can never echo a configured key into MCP-visible errors. The typesafe transport routes its SDK through the standalone undici fetch, because SDK 0.6.0 has an open bug where a cancelled call kills the whole process on Node 20 and 22 ([typesafe-sdk-js#2](https://github.com/typesafe-ai/typesafe-sdk-js/issues/2), fixed upstream in [nodejs/undici#4804](https://github.com/nodejs/undici/pull/4804)); the vercel transport goes through its own SDK. No retry or deadline uniformity is claimed for those two. Research and the original report: [issue #23](https://github.com/jkudish/jev-mcp/issues/23) by oppih.
|
|
239
637
|
|
|
240
638
|
### Vercel
|
|
241
639
|
|
|
@@ -249,6 +647,19 @@ With `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` set (and no other provid
|
|
|
249
647
|
|
|
250
648
|
If you already have an OpenRouter key, that is all you need: with no `TYPESAFE_API_KEY` present, every call goes through OpenRouter's Decisions API at identical pricing. The endpoint is alpha and adds a hop, and OpenRouter serves pinned versions rather than a `latest` alias, so the default `jev-latest` maps to `typesafe/jev-1.13` there. Direct TypeSafe remains the recommended default when you have both keys.
|
|
251
649
|
|
|
650
|
+
### Jev-compatible endpoints
|
|
651
|
+
|
|
652
|
+
For a service that implements the same System One request and response contract, set the provider explicitly:
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
export JEV_PROVIDER=compatible
|
|
656
|
+
export JEV_API_BASE_URL=https://api.openjev.sh/v1/systemone
|
|
657
|
+
export JEV_API_KEY=your-compatible-provider-key
|
|
658
|
+
export JEV_MCP_MODEL=openjev
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
The server sends `POST` requests with `{ model, state, questions }` and requires the standard response shape: an `answers` object plus a `usage` object reporting `input_tokens` and `output_tokens`, with an optional `model` string echoing the model that answered. Envelope problems (a non-object body or `answers`, malformed `usage` counts, a non-string `model`) are rejected at the transport boundary. Individual answers are not judged here: each tool validates them under its own `invalid_response` contract, so a missing or malformed answer fails closed in the tool instead of aborting the call. `JEV_API_BASE_URL` must be the full endpoint URL including the `/v1/systemone` path; it is used verbatim, with no trailing-slash or path normalization. The endpoint and credentials are kept in the local process environment. This adapter is provider-neutral; OpenJEV is one example, not a hard-coded dependency.
|
|
662
|
+
|
|
252
663
|
## Also in the family
|
|
253
664
|
|
|
254
665
|
Need those judgments to drive a real browser? [Jev Browser](https://github.com/jkudish/jev-browser) gives an agent a task and a URL and lets Jev pick the actions: click, type, select, stop. It uses the same judgment style this server exposes. The npm package is [@jkudish/jev-browser](https://www.npmjs.com/package/@jkudish/jev-browser).
|