Skip to content
rotkonetworksPublic

About

jevx is a compiler from vim-regex-flavoured line inputs to JSON responses for token efficient tool usage

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

jevx

jevx is developed in zish, at feats/jevx/. rotkonetworks/jevx is a read-only mirror of that directory, for using jevx without zish: send issues and changes to zish.

jevx asks TypeSafe's Jev model typed questions about some input and prints the answers as plain lines a shell can use: a probability, a chosen option, a score, and an exit status.

$ jevx 'urgent? Does this convey urgency?' <<<'Help! My payouts have been failing for 3 days.'
0.95

$ jevx -l 'fruit?>.5 Is \0 a fruit?' < words        # grep, by meaning
apple
banana

Jev is a decision model. It does not write text. You give it a state (what you are asking about) and some typed questions (yes/no, pick one, score), and it returns one answer per question with a probability. The API takes JSON; jevx turns a short one-line question into that JSON, so a script never hand-writes nested quoting to ask "is this urgent?".


Quick start

jevx is a zish feat in the tooling tier, and it is also a standalone program: one static binary that needs only curl. It never starts zish.

With zish — install it from the index, or build and stage it from the zish tree:

gf install jevx
zig build -Dfeats=jevx -Doptimize=ReleaseSafe --prefix ~/.zish/feats -Dfeat-layout=registry

Inside zish it resolves as a command.

Without zish — build this directory on its own (the mirror, or feats/jevx/ in a zish checkout) and put the binary on PATH:

zig build -Doptimize=ReleaseSafe
install -m755 zig-out/bin/jevx ~/.local/bin/

Give it an OpenRouter key, in either place:

# with zish
printf '%s\n' 'sk-or-…' > ~/.zish/openrouter.key && chmod 600 ~/.zish/openrouter.key
# standalone
mkdir -p ~/.config/jevx && printf '%s\n' 'sk-or-…' > ~/.config/jevx/openrouter.key \
  && chmod 600 ~/.config/jevx/openrouter.key

or set OPENROUTER_API_KEY.

Ask something:

$ echo 'Hi, how much is the pro plan?' | jevx 'team/ Which team? \| billing \| technical \| sales'
sales 0.97

Questions

Each question is one argument (or one line of a script):

[KEY] SIGIL [GATE] SPACE INSTRUCTIONS { \| ALTERNATIVE }
Sigil Type Answers with Alternatives
? noul — yes/no P(yes), 0..1 optional y: … / n: … describing each side
/ choice — pick one the option and a confidence 2–255 options: name or name: description
# score — rate on a rubric a level index (may land between levels) and a confidence 2–10 levels, ordered low → high
jevx 'urgent? Is the customer blocked right now? \| y: cannot operate \| n: a question'
jevx 'team/ Which team? \| billing: payments, refunds \| technical: bugs, outages \| sales'
jevx 'mood# How frustrated is the customer? \| calm \| annoyed \| angry \| furious'

KEY ([A-Za-z0-9_.-]+) names the answer. Leave it out and questions are named q1, q2, … by position. Keys must be unique.

Choice options are what comes back, so make them short; the description after : is what Jev reads. An option with no description is fine.

Score levels are descriptions only. The answer is the probability-weighted index: 0 is the first level, 1.04 is "just past the second".

Escapes

Like a vim pattern:

Write Means
| separates alternatives
\\ a literal backslash
\n a newline
\0 the current line, in lines mode (an error elsewhere)
anything else, e.g. \d passed through as written

Backticks need no escaping, and they matter: in a question, `user.plan` points Jev at that field of a JSON state.

Many questions, one request

Every question on the command line goes out in one request and is evaluated in parallel against the same state:

$ jevx 'urgent? Is it urgent?' 'team/ Which team? \| billing \| technical \| sales' \
       'refund? Does the customer ask for money back?' <<<"$ticket"
urgent 0.95
team billing 0.96
refund 0.08

Asking speculatively costs almost nothing, so ask everything you might need and let your code decide what to use.

Raw questions

For a question the syntax cannot express (structured instructions or criteria), pass the JSON question object with -J KEY=JSON:

jevx -J 'dup={"type":"noul","instructions":{"candidate":{"name":"J. Smith"},"question":"Is this resume for `candidate`?"}}' < resume.txt

Gates and exit status

A gate after the sigil turns the answer into an exit status, the way grep -q does:

Type Gate Example
noul >N >=N <N <=N, N in [0,1] spam?>.8
choice =OPT or !=OPT, then optional ~CONF team/=billing~.8
score >N >=N <N <=N, N a level index, optional ~CONF mood#>=2
choice/score ~CONF alone: confidence only team/~.9

~CONF requires the answer's confidence to be at least CONF (0..1).

jevx -q 'spam?>.8 Is this unsolicited bulk marketing?' < mail && mv mail spam/
Exit Meaning
0 answered, and every gate passed
1 answered, and a gate failed (in -l: nothing printed)
2 could not decide — usage error, no key, network/HTTP failure, malformed answer

1 and 2 are split on purpose, as in grep. jevx -q … && act never acts on an outage, and || fallback can tell "no" from "unknown":

jevx -q 'refund?>.7 Does the customer ask for money back?' <<<"$t"
case $? in
  0) route refunds ;;
  1) route default ;;
  *) route human ;;   # jevx could not decide
esac

Gates are checked when the question is parsed: a gate naming an option the choice does not offer, a threshold outside its range, or nan/inf is a usage error, never a gate that silently always fails.


State

The state is what the questions are about.

Source
stdin the default
-s TEXT inline
-S FILE a file (-S - is stdin)
script arguments see scripts

State that parses as a JSON object or array is sent as structure, so questions can point into it; anything else is text. -t forces text.

$ echo '{"user":{"plan":"free"},"msg":"we need SSO by friday"}' |
  jevx 'paid? Is `user.plan` a paid plan?' 'ent? Does `msg` ask for an enterprise feature?'
paid 0.02
ent 0.9

stdin is read once and capped at 16 MiB.


Lines mode

-l is the grep shape: every non-empty stdin line is an item, and every question is asked once per item, all in one request.

$ printf 'apple\ntypesafe\nbanana\nvertex\n' | jevx -l 'fruit?>.5 Is \0 the name of a fruit?'
apple
banana
  • \0 in a question is the current line. A question without \0 is asked "regarding" the line.

  • With a gate, the lines that pass are printed; -v prints the ones that fail. Exit 0 if anything was printed, 1 if not.

  • Without a gate, every line is printed after its answers, tab-separated:

    0.99	red 0.97	apple
    0.02	none 0.83	typesafe
    
  • Requests are packed by size: as many items as fit Jev's context go into each request, and the rest go out in further requests automatically. -b N additionally caps items per request (default 100, at most 1000). An item too big to fit even alone is an error that names it — split it.

  • -s/-S adds a shared state every item is judged against.

-0: multi-line items

-0 (or set nul) is -l with NUL-separated items, in and out, so an item can span lines — a function, a paragraph, a diff hunk. Feed it anything that prints NUL-separated pieces (the find -print0, git grep -z convention), such as a function splitter of your own:

# your-splitter: any tool that prints NUL-separated pieces
your-splitter src/*.c | jevx -0 -s "$manifest" \
  'net? Does the code in \0 open network connections that `manifest` does not mention?'

Gated output is the matching items, NUL-terminated; ungated output is ANSWERS<TAB>ITEM<NUL> per item.

Split big inputs

Jev judges a small piece far better than a big one. Asked once about a whole 95 KB source file, it answered at confidence 0.28; split into 35 function-sized pieces with -0, most scored near zero and the few that stood out named the right lines. When the input is large, split it at its natural seams (lines, paragraphs, functions, records), ask per piece, and combine in code — take the maximum for a red flag, count for a tally.

Why the line goes inside the question

Each line travels inside its own question (as {"item": LINE, "question": "… \item` …"}`), not as an index into one shared list. The shared-list layout was measured to be much worse — answers drifted with list position — which is Jev's known weakness with indirection over a large state.


Output

stdout is data, stderr is diagnostics. One line per question:

Type Line
noul KEY P
choice KEY OPTION CONFIDENCE
score KEY SCORE CONFIDENCE
urgent 0.95
team billing 0.96
mood 1.04 0.94

With a single question the key is dropped, so x=$(jevx '? …') is the bare value.

Numbers print to 3 decimals with trailing zeros dropped (0.906, 0.95, 1), in lines, -p and -e alike. Gates compare the unrounded value, and -j shows the response exactly as it came. The precision is one constant, DECIMALS in main.zig.

Flag Output
-p adds the distribution, in the order you wrote it: team billing 0.99 billing=0.99 technical=0.01 sales=0; score levels by index 0=0 1=0.96 2=0.04
-e shell assignments for eval (see export)
-q nothing; the exit status is the answer
-j the raw API response
-n the compiled request; nothing is sent

-n is how you learn what a line compiles to:

$ jevx -n -s 'Help!' 'u?>.7 urgent? \| y: time-sensitive \| n: no urgency'
{"state":"Help!","model":"typesafe/jev-1.13","questions":{"u":{"type":"noul","instructions":"urgent?","criteria":{"true":"time-sensitive","false":"no urgency"}}}}

-j shows what came back, including the exact model version and the cost:

{"model":"typesafe/jev-1.13-20260917","answers":{"q1":{"type":"noul","noul":0.91}},
 "usage":{"input_tokens":280,"output_tokens":21,"cost":0.00001176},"provider":"TypeSafe"}

Errors are one line on stderr, exit 2:

jevx: bad answer for team: choice is not one of the options
jevx: HTTP 400: Model bogus does not exist

Scripts: .jevx

A .jevx file is a set of questions you can execute: a rubric you keep, version and reuse.

#!/usr/bin/env jevx
" triage.jevx — route a support ticket
"   ./triage.jevx < ticket        ./triage.jevx "ticket text"
set probs

team/ Which team should handle this ticket?
  \| billing: payments, invoices, refunds, payouts
  \| technical: bugs, outages, integrations
  \| sales: pricing, upgrades, new accounts
urgent?>.6 Is the customer blocked right now?
  \| y: cannot operate, money not moving
  \| n: a question or minor annoyance
mood# How frustrated is the customer?
  \| calm \| annoyed \| angry \| furious
$ chmod +x triage.jevx
$ ./triage.jevx "Help! My payouts have been failing for 3 days."
team billing 0.99 billing=0.99 technical=0.01 sales=0
urgent 0.78
mood 2.08 0.77 0=0 1=0.07 2=0.78 3=0.15

File syntax

Vimscript conventions:

  • " starts a comment line.
  • A line starting with \ continues the previous one. The \ and the blanks before it are dropped and nothing is inserted, so \ more words supplies its own space. A \| line keeps its \|: it starts an alternative.
  • The first line may be a #! line.
  • set lines are options; anything else is a question.

The same format, without set and #!, is what -f FILE reads.

set

Several per line, like vim: set lines probs.

set Flag
lines -l lines mode
nul -0 lines mode, NUL-separated items
probs -p distributions
quiet -q exit status only
text -t state is always text
invert -v print failing lines
json -j raw response
local --local only a local Shingi; see Local
export -e shell assignments
export=PFX -E PFX … with a prefix other than jev_
model=M -m M model
batch=N -b N items per request

An unknown name is an error, so a typo (set line) never runs in the wrong mode. set options apply first; command-line flags add to them, and a command-line -m or -b wins.

Running

Shebang When
#!/usr/bin/env jevx jevx is on PATH
#!/usr/bin/env -S zish -c 'feat run jevx -x "$0" "$@"' through zish's feat registry

A first argument ending in .jevx, or -x FILE as the first argument, is script mode: jevx triage.jevx < ticket works as well.

Arguments are the state. Words after the script are joined with spaces, like "$*"; with none, the state is stdin. Flags still work: ./triage.jevx -q "text". Arguments together with -s/-S are refused.

Export to the shell

-e (or set export) prints assignments instead of the table:

$ eval "$(./triage.jevx -e < ticket)"
$ echo "$jev_team $jev_team_conf $jev_urgent"
billing 0.99 0.78
Type Variables
noul jev_KEY
choice jev_KEY (the option), jev_KEY_conf
score jev_KEY (the score), jev_KEY_conf
  • Names are prefix + key, with every byte outside [A-Za-z0-9_] turned into _. The prefix (default jev_, change it with -E PFX or export=PFX) stops a question named PATH or IFS from assigning that variable.
  • Values are single-quoted, so eval never expands them.
  • Two keys that would land on the same variable (a-b and a_b) are an error.
  • -e does not combine with -l.

Options

jevx [opts] QUESTION...
jevx SCRIPT.jevx [opts] [WORDS...]      jevx -x FILE [opts] [WORDS...]
Option
-s TEXT state, inline
-S FILE state from a file (- = stdin)
-t send state as text even if it is JSON
-l lines mode
-0 lines mode with NUL-separated items (multi-line pieces)
-v with -l and a gate: print lines that fail
-b N items per request in -l (1–1000, default 100)
-f FILE read questions from a file
-J KEY=JSON a raw question object
-x FILE run a script (must be first)
-p add probability distributions
-e / -E PFX print shell assignments
-q print nothing
-j print the raw response
-n print the request, send nothing
-m MODEL model (default typesafe/jev-1.13)
--local only a local Shingi over a checked Unix socket; nothing leaves the machine
--mock FILE replay canned responses instead of the network
-h usage

Backends, models, keys

Variable
JEVX_MODEL model, like -m
JEVX_BACKEND=typesafe talk to api.typesafe.ai directly (model jev-latest)
JEVX_BACKEND=shingi the same as --local
JEVX_SOCKET the --local socket, instead of $XDG_RUNTIME_DIR/jevx/shingi.sock
JEVX_ENDPOINT a custom URL (https only)
JEVX_API_KEY the key for JEVX_ENDPOINT, and only for it

By default jevx calls OpenRouter's Decisions API, POST https://openrouter.ai/api/alpha/decisions, with model typesafe/jev-1.13. OpenRouter does not accept TypeSafe's jev-latest alias, so the version is pinned; -j shows the exact build that answered (typesafe/jev-1.13-20260917). Pin deliberately if you tune thresholds against a version.

Keys:

Backend Key
OpenRouter ~/.zish/openrouter.key, else $XDG_CONFIG_HOME/jevx/openrouter.key (default ~/.config/jevx/), else $OPENROUTER_API_KEY
TypeSafe the same with typesafe.key, else $TYPESAFE_API_KEY
custom endpoint $JEVX_API_KEY only

A key file must be a regular file you own, not a symlink, with no group or other permission bits (chmod 600) — the rule ssh uses for private keys. A key file that fails it is refused, not skipped.

Local: Shingi on this machine

--local (or set local in a script, or JEVX_BACKEND=shingi) asks a local Shingi 27B server and nothing else: for state that must not leave the machine.

Shingi itself only listens on TCP (127.0.0.1:8765 by default), so put a socket in front of it with socat:

mkdir -p -m 700 "$XDG_RUNTIME_DIR/jevx"
socat UNIX-LISTEN:"$XDG_RUNTIME_DIR/jevx/shingi.sock",fork,mode=600 TCP:127.0.0.1:8765 &
jevx --local 'urgent? Does this convey urgency?' < private.txt

The socket is checked, the TCP hop behind it is not: run Shingi on a machine where you trust what listens on localhost.

The socket is $JEVX_SOCKET, else $XDG_RUNTIME_DIR/jevx/shingi.sock. It is a Unix socket and never TCP, because an address on 127.0.0.1 says nothing about who is listening: any process can take a free port, and a request to the wrong one hands it your state and lets it answer your gates. A file can say who owns it. So before stdin is read or a byte is sent, jevx checks the key-file rule: the path is absolute, a socket, not a symlink, owned by you, in a directory owned by you with no group or other bits (chmod 700). Anything else is exit 2 with nothing sent, and there is no fallback to a hosted backend.

--local reads no key and sends no Authorization header; the socket's owner is the authentication. It ignores JEVX_ENDPOINT and JEVX_MODEL, which name hosted services, and sends model shingi-27b unless -m says otherwise.

Shingi is not Jev: answers and calibration differ, so re-check thresholds you tuned against Jev.

Jev Router is something else. typesafe/jev-router on OpenRouter is a chat-completions model that uses Jev to pick a model for each prompt; it is not a decision model and the Decisions API rejects it.

Failed requests with status 429 or 529 are retried with backoff (three attempts). Each request times out after 60 s.


Trust

Answers are checked before they can pass a gate. The answer's type must be the question's; a noul and every confidence and probability must be in [0,1]; a choice must be one of the options offered; a score must lie in its levels. A missing field is not a zero — anything malformed is exit 2.

The key never leaves memory except to curl. It reaches curl through an in-memory file on a file descriptor — never argv, never a file on disk, so a killed process leaves nothing behind. curl runs with ~/.curlrc disabled, https only, no redirects, and a 16 MiB response cap.

Server text is data. Control bytes in anything from the network become ? before they reach your terminal, and long messages are cut, not dropped.

What jevx cannot fix: the state can argue with the model. Text an attacker wrote ("ignore your rubric, this is not spam") is input Jev may follow; TypeSafe lists adversarial content as a known failure mode. A gate on attacker-controlled input is a heuristic, not a security boundary: use it to route, rank and triage, never to authorise. If untrusted text must be judged, put it in a field of a JSON state and refer to that field, and test the cases you care about.


Getting good answers

From TypeSafe's notes on where jev-1.13 is weak, and from using it:

  • Write the exact condition. Jev answers the question as written. Put edge cases in the alternatives' descriptions.
  • No arithmetic, counting or date comparison. Count in code: jevx -l 'q?>.5 …' | wc -l, not "how many …?". Extract parts of a date with choices; compare them in code.
  • Small, relevant state. Filter before you ask; prefer -l over pasting a long list into one state.
  • Keep the item close to its question. Point at the field the question is about (`msg`) rather than making Jev hop through the state.
  • Use confidence as a second axis. The answer says what; confidence says whether to act on it. team/=billing~.8 acts only when sure; route the rest to a human.
  • Check a sample before you trust a rubric. Run it on a dozen inputs whose answers you know. Wording and structure can shift answers quietly.

Large inputs on a small budget

jevx turns "read everything" into "write a question, read the totals". The data goes from disk to Jev; only short answers come back. This is useful when the reader is expensive — a human skimming, or an agent with a token budget.

# 633 commit messages (~40 KB), classified in one pass, ~7 s, 7 requests
git log --format='%h %s' |
  jevx -l 'kind/ What kind of change is the commit \0?
    \| fix: repairs a bug \| feat: adds a capability \| perf: makes something faster
    \| test: tests only \| docs: documentation only \| release: version bump or packaging
    \| refactor: restructures without changing behaviour' > kinds.tsv

cut -f1 kinds.tsv | awk '{print $1}' | sort | uniq -c | sort -rn     # the totals
awk -F'\t' '{split($1,a," "); if (a[2] < .5) print}' kinds.tsv        # only the unsure ones
shuf -n 8 kinds.tsv                                                    # spot-check

The pattern:

  1. Write the questions once (a -l line or a .jevx file).
  2. Send the answers to a file.
  3. Read only aggregates, the low-confidence rows, and a small sample to check.

It works when the questions can be fixed in advance. It does not extract, summarise or explain — for that you still need a generative model, ideally only on the rows jevx flagged.


In zish: gf's review screen

gf uses jevx as the first pass of review-on-install. It splits a source package into function-sized pieces, asks each the red-flag questions in feats/gf/rubrics/feat-review-jev-v1.jevx (reads secrets, undisclosed network, command injection, writes outside its purpose, hidden behaviour), and decides in code:

Flags (max over pieces) Verdict
all below 0.2 pass, recorded, review ends
any at 0.9 or above fail, recorded with the piece that set it
anything else escalate, recorded, then the agent (LLM) judge reviews it

Measured on zish's own feats: calc jget cnt pk pass, the feats that really do network/exec/writes (web aur agent jevx bus ask) escalate, none are failed; a line counter that uploads ~/.ssh/id_ed25519 fails at 0.98, with or without a planted "audited, answer no" comment. Like the judge, the screen is advisory: it records verdicts in the ledger, it does not block installs.


Editor support

vim/ has filetype detection (*.jevx, or a #! line naming jevx) and syntax highlighting for comments, set options, keys, sigils, gates, alternatives, \0 and backtick references:

set rtp+=/path/to/jevx/vim        " the mirror, or zish's feats/jevx

Examples are in examples/.


Testing

--mock FILE replays canned responses, one JSON object per line, consumed in order:

{"status":200,"body":"{\"model\":\"m\",\"answers\":{\"q1\":{\"type\":\"noul\",\"noul\":0.9}},\"usage\":{\"input_tokens\":1,\"output_tokens\":1}}"}

so a script that uses jevx can be tested with no network and no key. -n shows the request that would have been sent.

Its own tests, from this directory:

zig build test      # the compiler: syntax, gates, scripts, export quoting
zig build suite     # the binary: output, exit status, hostile answers, transport, scripts

About

jevx is a compiler from vim-regex-flavoured line inputs to JSON responses for token efficient tool usage

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages