Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions _posts/2026-10-03-jev-plays-chess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
---
layout: post
comments: true
title: "Jev Plays Chess: Building a Decision Harness Around a Model"
excerpt: "A small experiment puts Jev in control of every chess move while code keeps the rules, candidate facts, and safety checks deterministic."
categories: genai
tags: [ai, agents, chess, typesafe]
toc: true
img_excerpt:
mermaid: true
---

[Jev](https://typesafe.ai/blog/introducing-system-one-models-and-jev) is TypeSafe AI's first public [System One model](https://typesafe.ai/blog/introducing-system-one-models-and-jev). It is built to answer typed questions with structured decisions. That makes it a natural fit for a game: ask it to choose among legal moves, then let ordinary code apply the answer.

Check failure on line 13 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L13 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":13,"column":2},"end":{"line":13,"column":5}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check notice on line 13 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L13 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":13,"column":71},"end":{"line":13,"column":73}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check warning on line 13 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L13 <write-good.TooWordy>

'It is' is too wordy.
Raw output
{"message":"'It is' is too wordy.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":13,"column":185},"end":{"line":13,"column":190}}},"severity":"WARNING","code":{"value":"write-good.TooWordy"}}

Check warning on line 13 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L13 <write-good.Passive>

'is built' may be passive voice. Use active voice if you can.
Raw output
{"message":"'is built' may be passive voice. Use active voice if you can.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":13,"column":188},"end":{"line":13,"column":196}}},"severity":"WARNING","code":{"value":"write-good.Passive"}}

Check notice on line 13 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L13 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":13,"column":188},"end":{"line":13,"column":190}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

In this article we explore [Jev Chess](https://github.com/dzlab/snippets/tree/master/jev-chess) a small TypeScript web app that uses Jev plays chess. An experiment exploring how to put a constrained decision model in a loop.

Check failure on line 15 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L15 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":15,"column":29},"end":{"line":15,"column":32}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check failure on line 15 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L15 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":15,"column":134},"end":{"line":15,"column":137}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

The project leverages [chess.js](https://github.com/jhlywa/chess.js/) for chess rules and legal moves, [react-chessboard](https://github.com/Clariity/react-chessboard) for rendering, a server-side game loop, and a Jev client. At each game turn, Jev chooses from a set of move candidates prepared by the harness.

Check failure on line 17 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L17 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":17,"column":215},"end":{"line":17,"column":218}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check failure on line 17 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L17 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":17,"column":246},"end":{"line":17,"column":249}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

## Let code own the rules

The first design choice was to keep the game state authoritative as part of the harness and away from the decision model. The harness uses `chess.js` library to find legal moves in a position, whether a move ends the game, and how a move updates the position. Jev gets a description of the current position and a choice question whose options refer to those legal moves.

Check notice on line 21 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L21 <write-good.E-Prime>

Try to avoid using 'was'.
Raw output
{"message":"Try to avoid using 'was'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":21,"column":25},"end":{"line":21,"column":28}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 21 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L21 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":21,"column":261},"end":{"line":21,"column":264}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

```mermaid
flowchart LR
subgraph harness[Chess harness]
direction TB
board["Chessboard state<br/>FEN + move history"]
rules["chess.js<br/>legal move objects"]
facts["Candidate facts<br/>captures, checks, recaptures<br/>mate and draw risks"]
request["Typed request<br/>state: { FEN, history, facts }<br/>questions.move: choice + criteria"]
validate["Validate returned ID<br/>look up original Move"]
apply["Apply with chess.js"]

board --> rules --> facts --> request
validate --> apply --> board
end

subgraph jev["Decision model"]
direction TB
choose["Compare move descriptions"]
response["Typed response<br/>answers.move.choice: move_017<br/>probabilities: one per option"]
choose --> response
end

browser["Browser view<br/>board, move list, probabilities"]

request -->|state + choice options| choose
response -->|move ID + distribution| validate
apply --> browser

classDef state fill:#e8f4ff,stroke:#1677b9,color:#0b2d42;
classDef rules fill:#efe8ff,stroke:#6a45b8,color:#241340;
classDef facts fill:#fff4cc,stroke:#b58100,color:#3d2b00;
classDef request fill:#e7f2ff,stroke:#2878b8,color:#102a43;
classDef response fill:#e8f8ef,stroke:#23834d,color:#0e3d24;
class board,browser state;
class rules,validate,apply rules;
class facts facts;
class request request;
class choose,response response;
```

The diagram above explains the overall architecture; the harness builds a candidate list from `chess.js`, assigns each move an internal ID, and asks Jev to select one. When the response arrives, it looks up that ID in the original candidate list and passes the associated move back to `chess.js`. If the model returns an unknown ID, the turn fails instead of applying an arbitrary move. See the [Jev client](https://github.com/dzlab/snippets/blob/master/jev-chess/src/server/jev-client.ts) and [game runner](https://github.com/dzlab/snippets/blob/master/jev-chess/src/server/game-runner.ts).

Check warning on line 63 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L63 <write-good.TooWordy>

'overall' is too wordy.
Raw output
{"message":"'overall' is too wordy.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":63,"column":32},"end":{"line":63,"column":39}}},"severity":"WARNING","code":{"value":"write-good.TooWordy"}}

Check failure on line 63 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L63 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":63,"column":150},"end":{"line":63,"column":153}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check failure on line 63 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L63 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":63,"column":397},"end":{"line":63,"column":400}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

This design keeps the model's job limited to judging the described options. The harness keeps responsibility for legal play and game state.

## Make candidate moves easier to compare

Sending Jev a list of candidates such as `Nf3`, `Qxd4`, and `O-O` that represent legal moves is not enough to make a decision, because the notation alone does not tell what the move threatens or leaves hanging. Therefore the harness needs to add additional information.

Check failure on line 69 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L69 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":69,"column":9},"end":{"line":69,"column":12}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check notice on line 69 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L69 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":69,"column":94},"end":{"line":69,"column":96}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check warning on line 69 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L69 <write-good.TooWordy>

'Therefore' is too wordy.
Raw output
{"message":"'Therefore' is too wordy.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":69,"column":212},"end":{"line":69,"column":221}}},"severity":"WARNING","code":{"value":"write-good.TooWordy"}}

Check warning on line 69 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L69 <write-good.TooWordy>

'additional' is too wordy.
Raw output
{"message":"'additional' is too wordy.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":69,"column":247},"end":{"line":69,"column":257}}},"severity":"WARNING","code":{"value":"write-good.TooWordy"}}

For a position, the harness summarizes the board, piece counts, and material using the simple values pawn 1, knight or bishop 3, rook 5, and queen 9. For each candidate move, it can describe captures, promotions, checks, immediate legal capture replies, whether the moved piece can be recaptured, and changes to attacks on occupied squares. It also checks whether the move allows an immediate checkmate or creates a threefold-repetition draw. The implementation is in [`decision-facts.ts`](https://github.com/dzlab/snippets/blob/master/jev-chess/src/server/decision-facts.ts).

Check warning on line 71 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L71 <write-good.Passive>

'be recaptured' may be passive voice. Use active voice if you can.
Raw output
{"message":"'be recaptured' may be passive voice. Use active voice if you can.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":71,"column":283},"end":{"line":71,"column":296}}},"severity":"WARNING","code":{"value":"write-good.Passive"}}

Check notice on line 71 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L71 <write-good.E-Prime>

Try to avoid using 'be'.
Raw output
{"message":"Try to avoid using 'be'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":71,"column":283},"end":{"line":71,"column":285}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check notice on line 71 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L71 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":71,"column":463},"end":{"line":71,"column":465}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

The harness also applies two narrow safety rules. When at least one candidate avoids immediate mate, it removes moves that allow mate in one. When the side to move is ahead by at least three material points, it avoids an immediate threefold draw if a non-drawing move remains. If every move carries the relevant risk, Jev still receives the available moves and a warning.

Check notice on line 73 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L73 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":73,"column":165},"end":{"line":73,"column":167}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 73 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L73 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":73,"column":319},"end":{"line":73,"column":322}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Here is an example request sent to Jev; it specifies the model name, and the state: the FEN encodes the board, side to move, castling rights, and move counters; the harness also marks that White is in check:

Check notice on line 75 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L75 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":75,"column":6},"end":{"line":75,"column":8}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 75 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L75 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":75,"column":36},"end":{"line":75,"column":39}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check notice on line 75 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L75 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":75,"column":196},"end":{"line":75,"column":198}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

```json
{
"model": "typesafe-ai/jev",
"state": {
"game": "standard chess",
"fen": "r1bq1rk1/ppp1bppp/3p1p2/3P4/8/P1N2N2/1Pn2PPP/R1BQKB1R w KQ - 0 9",
"side_to_move": "white",
"is_check": true,
"legal_move_count": 3,
"current_side_material_lead": 1
}
}
```

The other part of the request `questions.move` asks Jev to select from the legal candidates. Each key is an ID the game runner can map back to its original move object; each value adds concrete facts about that move:

Check failure on line 91 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L91 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":91,"column":53},"end":{"line":91,"column":56}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check notice on line 91 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L91 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":91,"column":103},"end":{"line":91,"column":105}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

```json
{
"questions": {
"move": {
"type": "choice",
"instructions": "Which legal chess move is best for the side to move? Consider king safety, tactics, material, and improving the position. Choose only from the supplied moves.",
"criteria": {
"move_000": "Qxc2 (d1 to c2); queen captures knight (+3 material points); black pawn on h7 is newly attacked by white; white pawn on a3 is no longer attacked by black; white rook on a1 is no longer attacked by black; white king on e1 is no longer attacked by black",
"move_001": "Kd2 (e1 to d2); king; white king on d2 is no longer attacked by black; opponent has immediate legal captures of pawn on a3, rook on a1",
"move_002": "Ke2 (e1 to e2); king; white king on e2 is no longer attacked by black; opponent has immediate legal captures of pawn on a3, rook on a1"
}
}
}
}
```

Here `move_000` is `Qxc2`, which captures the checking knight. The actual request combines this choice question with the fuller state above. Jev returns a candidate ID, and the game runner maps that ID back to the corresponding legal move rather than parsing generated chess notation.

Check notice on line 109 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L109 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":109,"column":17},"end":{"line":109,"column":19}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 109 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L109 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":109,"column":142},"end":{"line":109,"column":145}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

The client posts this JSON to `https://ai-gateway.vercel.sh/v1/evaluate`, the API response names a candidate ID; which gets mapped back to the legal move object by the game runner.

If Jev returns `move_000`, the runner finds that ID in the supplied candidate list and applies the associated `from`, `to`, and promotion fields. If the response names an ID that was never offered, the turn errors instead of trying to parse it as a move.

Check failure on line 113 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L113 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":113,"column":4},"end":{"line":113,"column":7}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check notice on line 113 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L113 <write-good.E-Prime>

Try to avoid using 'was'.
Raw output
{"message":"Try to avoid using 'was'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":113,"column":180},"end":{"line":113,"column":183}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

## Handle positions with many choices

Most chess positions fit within the project's configured limit of 48 options (a configurable harness limit; TypeSafe's [Choice documentation](https://docs.typesafe.ai/primitives/choice) allows up to 255 options). When a position has more candidates, the runner splits them into balanced groups rather than dropping legal moves. Jev picks a finalist from each group, then chooses among the finalists in another request.

Check failure on line 117 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L117 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":117,"column":329},"end":{"line":117,"column":332}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

```mermaid
flowchart TD
A[Eligible moves] --> B{More than 48?}
B -- No --> C[Final choice request<br/>up to 48 options]
B -- Yes --> D[Split into balanced groups<br/>up to 48 options per group]
D --> E[Jev selects one winner<br/>from each group]
E --> F[Collect group winners]
F --> B
C --> G[Final choice<br/>move ID + probabilities]
G --> H[Map ID to original legal move]

classDef moves fill:#e8f4ff,stroke:#1677b9,color:#0b2d42;
classDef group fill:#fff4cc,stroke:#b58100,color:#3d2b00;
classDef final fill:#e8f8ef,stroke:#23834d,color:#0e3d24;
class A,B,H moves;
class D,E,F group;
class C,G final;
```

For example, 49 candidates become groups of 25 and 24. Jev chooses one winner from each group, then makes a final choice between those two finalists. Larger sets repeat the group-selection round until the finalists fit under the limit.

Check failure on line 138 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L138 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":138,"column":56},"end":{"line":138,"column":59}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

This bracket was improved after reviewing an early game. At one position there were 49 legal moves, and the first version split them into groups of 48 and 1. The single-option group was not a real decision. The updated grouping distributes options as evenly as possible, and the prompt distinguishes group selections from the final choice. The bracket keeps every candidate in the process, at the cost of extra requests.

Check warning on line 140 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L140 <write-good.Passive>

'was improved' may be passive voice. Use active voice if you can.
Raw output
{"message":"'was improved' may be passive voice. Use active voice if you can.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":140,"column":14},"end":{"line":140,"column":26}}},"severity":"WARNING","code":{"value":"write-good.Passive"}}

Check notice on line 140 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L140 <write-good.E-Prime>

Try to avoid using 'was'.
Raw output
{"message":"Try to avoid using 'was'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":140,"column":14},"end":{"line":140,"column":17}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check notice on line 140 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L140 <write-good.E-Prime>

Try to avoid using 'were'.
Raw output
{"message":"Try to avoid using 'were'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":140,"column":80},"end":{"line":140,"column":84}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check notice on line 140 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L140 <write-good.E-Prime>

Try to avoid using 'was'.
Raw output
{"message":"Try to avoid using 'was'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":140,"column":183},"end":{"line":140,"column":186}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

## A bounded follow-up run

I recorded a short self-play run where Jev self-played 20 plies, or 10 moves per side, then collected some stats:

Check failure on line 144 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L144 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":144,"column":40},"end":{"line":144,"column":43}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

| Measurement | Result |
|---|---:|
| Moves applied | 20 plies |
| Jev API responses | 21 |

Check failure on line 149 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L149 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":149,"column":3},"end":{"line":149,"column":6}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}
| Choices from the supplied candidate list | 21 of 21 |
| API or move-application errors | 0 |
| Response latency, median | 355 ms |
| Response latency, mean | 4.7 s |
| Response latency, range | 212 ms to 26.4 s |

The mean is pulled up by a few long responses; the median better represents a typical request in this small sample. All 20 moves suggested by Jev were accepted by `chess.js`. Here is a recdoring of the game:

Check warning on line 156 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L156 <write-good.Passive>

'is pulled' may be passive voice. Use active voice if you can.
Raw output
{"message":"'is pulled' may be passive voice. Use active voice if you can.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":156,"column":10},"end":{"line":156,"column":19}}},"severity":"WARNING","code":{"value":"write-good.Passive"}}

Check notice on line 156 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L156 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":156,"column":10},"end":{"line":156,"column":12}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 156 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L156 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":156,"column":143},"end":{"line":156,"column":146}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check warning on line 156 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L156 <write-good.Passive>

'were accepted' may be passive voice. Use active voice if you can.
Raw output
{"message":"'were accepted' may be passive voice. Use active voice if you can.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":156,"column":147},"end":{"line":156,"column":160}}},"severity":"WARNING","code":{"value":"write-good.Passive"}}

Check notice on line 156 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L156 <write-good.E-Prime>

Try to avoid using 'were'.
Raw output
{"message":"Try to avoid using 'were'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":156,"column":147},"end":{"line":156,"column":151}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check notice on line 156 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L156 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":156,"column":181},"end":{"line":156,"column":183}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 156 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L156 <Vale.Spelling>

Did you really mean 'recdoring'?
Raw output
{"message":"Did you really mean 'recdoring'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":156,"column":186},"end":{"line":156,"column":195}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

<video controls preload="metadata" poster="https://raw.githubusercontent.com/dzlab/snippets/master/jev-chess/assets/jev-chess-run.png" width="100%">
<source src="https://raw.githubusercontent.com/dzlab/snippets/master/jev-chess/assets/jev-chess-run.webm" type="video/webm">
Your browser may not support inline WebM playback. [Open or download the recording](https://github.com/dzlab/snippets/blob/master/jev-chess/assets/jev-chess-run.webm).
</video>

[Open or download the 20-ply recording](https://github.com/dzlab/snippets/blob/master/jev-chess/assets/jev-chess-run.webm).

> Note: the probability distribution returned by Jev is a relative weighting over the options it received, not a prediction of the chance of winning the game.

Check failure on line 165 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L165 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":165,"column":50},"end":{"line":165,"column":53}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check notice on line 165 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L165 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":165,"column":54},"end":{"line":165,"column":56}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}


## Improving the harness

This experiment shows that the chess rules and safety checks are much easier to verify when implemented in the harness. And that Jev can only compare the facts and options the harness provides.

Check notice on line 170 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L170 <write-good.E-Prime>

Try to avoid using 'are'.
Raw output
{"message":"Try to avoid using 'are'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":170,"column":62},"end":{"line":170,"column":65}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 170 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L170 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":170,"column":130},"end":{"line":170,"column":133}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Also having guardrails in the harness matter. A legal move can still lose immediately. Filtering an obvious mate-in-one risk is a useful boundary check, not a substitute for search.

Check notice on line 172 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L172 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":172,"column":126},"end":{"line":172,"column":128}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

These observations matches the recommendation from TypeSafe's [building guidance](https://docs.typesafe.ai/concepts/how-to-build-with-system-one) that describes a useful pattern: keep control flow and deterministic rules in code, give Jev only the state needed for a narrow judgment, and combine its structured answers in the application. It also recommends using confidence to route uncertain cases instead of treating every valid answer as equally reliable.

Check failure on line 174 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L174 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":174,"column":236},"end":{"line":174,"column":239}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Jev Chess is a small experiment, which has a lot of room for improvement. For example, on the strategic judgments of Jev, we could try a few narrow, independent questions on the finalists—such as which move best improves king safety and which best creates a threat—and combine their results with the deterministic facts in code. TypeSafe documents parallel independent questions as a way to decompose a broad judgment without adding serial calls.

Check failure on line 176 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L176 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":176,"column":1},"end":{"line":176,"column":4}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}

Check notice on line 176 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L176 <write-good.E-Prime>

Try to avoid using 'is'.
Raw output
{"message":"Try to avoid using 'is'.","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":176,"column":11},"end":{"line":176,"column":13}}},"severity":"INFO","code":{"value":"write-good.E-Prime"}}

Check failure on line 176 in _posts/2026-10-03-jev-plays-chess.md

View workflow job for this annotation

GitHub Actions / vale

[vale] _posts/2026-10-03-jev-plays-chess.md#L176 <Vale.Spelling>

Did you really mean 'Jev'?
Raw output
{"message":"Did you really mean 'Jev'?","location":{"path":"_posts/2026-10-03-jev-plays-chess.md","range":{"start":{"line":176,"column":118},"end":{"line":176,"column":121}}},"severity":"ERROR","code":{"value":"Vale.Spelling"}}


---

_I hope you enjoyed this article. Feel free to leave a comment or reach out on twitter [@bachiirc](https://twitter.com/bachiirc)._
Loading