Sashité Engine Interface (SEI) Specification
- Version: 1.0.0-rc.15
- Status: Release candidate
- Author: Cyril Kato
- Published: September 27, 2026
- License: Open Web Foundation Agreement 1.0
1. Status of this document
This document is a release candidate: its normative text is complete, and only errata are expected before 1.0.0. The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as normative requirements. Passages marked informative are not normative.
Capitalized terms (Active Player, Initial Position, Legal Move, Move, Pass Move, Piece, Position, Rule System, Style) are defined in the Glossary. This document does not redefine those terms.
2. Overview
SEI is a communication protocol between a host program (user interface, server, bot, test tool) and a game engine, for games of the chess family. It fills the role of UCI without being tied to any particular game.
2.1 Principles
- One thing at a time. The engine processes requests one by one, in order. Any request other than
pingfirst interrupts the ongoing search. pingstands apart.pingis accepted in every state, answers immediately, and changes nothing.- One request, one end. Every request receives exactly one terminal response.
- Progress may be lost, the result never.
infois optional and may be dropped;donecarries the complete result. - Self-contained requests. A search carries everything that determines its result, and
configuredeclares the complete configuration. - Fatality is read from
re. An error attached to a request is never fatal; an error withoutrealways is. - Safety through the deadline, strategy through planning. One field bounds the search; the others only inform it.
- Active Player’s perspective. Scores, clocks, and mate distances are expressed from the Active Player’s point of view.
- Game independence. SEI knows games only through FEEN, PMN, and SIN, and through a rules identifier that fixes how a given game uses them.
- Data and diagnostics kept apart. The protocol uses standard output; diagnostics use standard error.
- Strict requests, tolerant events, additive evolution.
3. Dependencies
| Specification | Version | Purpose |
|---|---|---|
| FEEN | 1.x | Positions (host to engine) |
| PMN | 1.x | Moves |
| SIN | 1.x | Styles |
| I-JSON (RFC 7493) | — | Serialization |
| JSON Pointer (RFC 6901) | — | Error locations |
SEI 1 depends on major version 1 of FEEN, PMN, and SIN. The engine emits only PMN and SIN.
Rules identifiers. A rules identifier is a non-empty, opaque string. It designates a rules document, which fixes everything SEI leaves to the game:
- the Rule System (geometry, legality, terminal positions, history-dependent endings and their thresholds);
- the canonical FEEN of every Position of the game;
- the canonical PMN of every Legal Move of the game: the EPIN letters of its Pieces, which Moves are written with
~, and the Piece that a drop names; - the SIN styles it admits, and their pairings.
Engine and host both conform to the rules document. A canonical Move or Position is one written as the rules document prescribes; SEI itself adds one rule, which every rules document inherits: a drop names its Piece (F*e5, never *e5). An engine MAY announce several identifiers that designate the same rules. The identifiers of Sashité’s games are listed in the companion SEI — Rules registry (§15); a vendor SHOULD name its own after a name it controls, such as a domain or project name.
4. Terminology
| Term | Definition |
|---|---|
| Host | Program that launches the engine and sends it requests |
| Engine | Program that responds according to SEI; it computes Moves, and is not a rules library |
| Session | Lifetime of the engine process, from launch until its standard output closes |
| Request | Message from the host |
| Event | Message from the engine |
| Terminal event | done or error attached to a request |
| Receipt | Instant at which the engine’s reader has read the complete line of a request (§5) |
| Active search | Validated search whose terminal event has not yet been emitted |
| Searched position | Position obtained by applying moves to position |
| Variation | Legal sequence of Moves from the searched position, together with its score |
first, second |
Player seats; first is the player whose pieces are uppercase in FEEN |
| Pairing | Ordered pair of styles (style of first, style of second), written as two uppercase SIN letters ("WJ") |
| Rules identifier | Non-empty, opaque string designating a rules document (§3), and thereby the game |
5. Transport
Channel. The host launches the engine as a child process. The protocol uses the engine’s standard input and standard output. Its standard error is free-form and serves diagnostics.
Launch.
- The host starts the engine from an argument vector fixed by its configuration, without a shell, with exactly three descriptors (its standard input, output, and error), in its own process group.
- The engine MUST NOT require any command-line argument, environment variable, or network access to speak SEI. It MAY accept arguments that fix resources (a working directory, a cache).
- A host that runs an engine it does not trust MUST run it as another user or in a sandbox, so that the engine cannot read the host’s secrets, and MUST bound its processor share, memory, and process count. The protocol confines the engine to its answers; only the deployment confines its process.
Lines.
- Each message is an I-JSON object written on a single line, terminated by LF (U+000A). Every sender flushes after each line.
- Senders write LF only. Receivers tolerate a CR before the LF and ignore blank lines.
- Every receiver MUST accept lines of at least 1 MiB, measured in bytes, and at least 16 levels of nesting. It MAY reject messages beyond these limits (§6.5).
- Totality. Every received line MUST produce a processing outcome defined by this document, without crash or deadlock. The resources consumed to process a line are bounded by its size, except resources explicitly requested by the configuration or the search.
Streams.
- The host MUST read the engine’s standard output continuously, and MUST read or redirect its standard error. It MUST NOT close it: an engine that writes a diagnostic to a closed stream may die of it.
- The engine MUST read its standard input continuously, from launch, so that every request is received as soon as it is written: receipt is the instant its reader has read the complete line, whatever the engine is doing at that moment. Every duration the engine counts down is counted from receipt; the time the engine then takes before acting on a request — ending the previous search, say — is the engine’s own. It SHOULD defer any heavy initialization until it processes
hello. - The engine SHOULD write from a dedicated thread, never from its search threads. It MAY drop any
info, never a terminal event. - The engine MUST NOT fail because its standard error cannot be written.
End of session.
- Requested by the host. The host ends the session by closing the engine’s standard input; this is the only provided means. The engine MUST then terminate promptly, and SHOULD do so within one second. It is not required to emit pending terminal events, and MUST exit with code 0.
- After a fatal error. After an error without
re, the engine MUST terminate with a non-zero exit code. - Detection. The host detects the end of the session when the engine’s standard output closes. Pending requests have then failed, and any later output is ignored. The host MAY kill the process group if it has not terminated after a grace period.
- Unexpected end. An end of session that the host did not request, and that no error without
repreceded, is an engine failure, whatever its exit code.
6. Messages
6.1 Forms
{"id":1,"op":"…", …} request (host → engine)
{"re":1,"ev":"…", …} event (engine → host)
In examples, messages are indented and prefixed with → or ←. On the wire, each message fits on one line.
6.2 Request envelope
A request carries:
id, an integer greater than or equal to 0, and strictly greater than that of every previous request whose envelope was valid;op, a string.
6.3 Numbers
Every number has an integer value, with an absolute value of at most 2^53 − 1. Durations are in milliseconds, and each field specifies its domain.
- Every sender MUST write numbers as plain integers: no fractional part, no exponent, and never
-0. - Every receiver MUST reject a value that is not an integer or that lies outside its domain. It MAY either accept or reject an integer written in another form, such as
1.0. - The engine MUST represent every value of the domain exactly.
6.4 Tolerance
Strict requests. Any field defined neither by the negotiated version nor by an announced feature causes invalid, including within a nested object. An unknown operation also causes invalid.
The hello exception. hello ignores its unknown fields.
Tolerant events. The host MUST ignore unknown fields and events of unknown type (ev), and apply the fallbacks of §9.5. An engine MAY add fields named x_<vendor>_<name> to its events.
Evolution. The rules of evolution are given in §13.
6.5 Envelope errors
Blank lines aside (§5), the following are envelope errors:
- a line that is not an I-JSON object (duplicate key, invalid encoding, a number whose absolute value exceeds 2^53 − 1…), a line that is too long, or a message nested too deeply for the receiver;
- an
idthat is missing, not an integer, negative, or not increasing; - an
opthat is missing or not a string; - at the time it is processed, any request other than
helloorpingsent before a successfulhello; - a
hellosent after a successfulhello.
The engine then emits an invalid error without re, which is fatal (§7). All other errors concern content and are attached to their request.
6.6 Permanent core
The transport (§5), the envelope (§6.1 to §6.5), hello, ping, and the error without re are identical in every version of SEI. Everything else is versioned: the descriptive fields of the done of hello (§8.1) refer to the version it returns.
7. Execution model (normative)
- Opening. Until a
hellohas succeeded, onlyhelloandpingare accepted. Ahellosucceeds when itsdonecarries aversion. Ahellothat did not succeed MAY be sent again. - One thing at a time. The engine processes requests other than
pingone by one, in order of receipt. Processing a request begins with its validation, which is not interruptible and whose cost is bounded by the line (§5). Before processing one, it ends the active search, if any, by emitting itsdonewith the best available result (§8.4). pingstands apart. The engine answerspingat its receipt, whatever processing is under way.pingnever changes state.- One request, one end. During the session, every request with a valid envelope receives exactly one terminal event carrying its
id. No event is attached to it afterwards. - Order. Terminal events follow the order of requests. The only exception: the
doneof apingmay precede that of any request received before it and still in progress. - Framing. Every non-terminal event attached to a request appears between the receipt of that request and its terminal event.
- Fatality. The presence of
realone determines what follows.- An error carrying
reis never fatal. The configuration and the presence of an active search are those that a request without effect would have left, apart from the interruption required by rule 2. - An error without
reis always fatal (§5). An engine that can no longer guarantee its correct operation MUST emit one, then terminate.
- An error carrying
7.1 Transitions (summary)
| From | Event | To |
|---|---|---|
| Any live state | ping |
Unchanged |
| Awaiting hello | done of hello carrying version |
Ready |
| Awaiting hello | done of hello without version, or error with re |
Awaiting hello |
| Ready | Valid search on a non-terminal position |
Searching |
| Ready | Any other request, or error with re |
Ready |
| Searching | End of the search | Ready |
| Searching | Any other request | Ready, then processing of the request |
| Any live state | Error without re, closing of input or output |
End of session |
8. Requests
8.1 hello
→ {"id":1,"op":"hello","versions":[1],"host":{"name":"Example host","version":"2.0"}}
← {"re":1,"ev":"done","version":1,"versions":[1],
"engine":{"name":"Example","version":"0.1.0"},
"rules":{"sashite.sanki.kernel/1":{"pairings":["WW","WJ","JW","JJ"]}},
"features":{"lines":{"max":64},"roots":{}},
"options":{
"threads":{"type":"int","default":1,"min":1,"max":64},
"hash":{"type":"int","default":16,"min":1,"max":4096}}}
The request carries:
| Field | Required | Content |
|---|---|---|
versions |
Yes | Non-empty array of integers greater than or equal to 1: the major versions the host speaks |
host |
No | name (required), version: how the engine’s diagnostics name the host |
A hello whose versions is missing or malformed receives invalid (with re); a malformed host is ignored. Any other hello answers with a done that describes the engine:
| Field | Content |
|---|---|
version |
Highest common version; absent if there is none |
versions |
Major versions the engine speaks |
engine |
name (required), version, author, about |
rules |
Implemented rules identifiers (§3), at least one, each with an object that MAY carry its pairings |
features |
Features and their parameters (§10) |
options |
Configurable options (§8.3) |
- Success. The opening succeeds only if
versionis present. Without a common version, the host has nothing else to offer and SHOULD close the session. - Descriptive fields.
rules,features, andoptionsdescribe the engine under the version returned inversion. - Pairings.
pairingsis a non-empty array of pairings (§4), each a string of two uppercase SIN letters: the style offirst, then the style ofsecond. Ifpairingsis absent, the engine supports every pairing the rules document defines.
8.2 ping
→ {"id":9,"op":"ping"}
← {"re":9,"ev":"done"}
ping is accepted in every state of the session. The engine answers it at its receipt, without waiting for the end of the processing under way (opening, configuration, or search), and SHOULD do so within 50 ms. Its form is frozen: it carries no field other than id and op.
Monitoring. The host SHOULD monitor the engine’s liveness with ping, especially during long operations. Once the engine has answered a first ping, a ping left unanswered beyond a grace period much longer than 50 ms, for example one second, indicates a hung engine: the host SHOULD then close the session. The duration of hello and configure is not timed; that of a search is bounded by its budget (§8.4).
Informative. ping attests the engine’s reading loop, not its search: an engine whose search threads spin answers ping and never emits done. The budget of §8.4 is what protects the host from that; the done of hello signals availability.
8.3 configure
→ {"id":2,"op":"configure","options":{"threads":4,"hash":256}}
← {"re":2,"ev":"done"}
configure declares the complete configuration of the engine.
- Complete declaration.
optionscarries the desired value of each option; any absent option takes its default value. Before the first successfulconfigure, every option has its default value. - Replacement. The effective configuration is that of the last successful
configure. It depends neither on the number nor on the order of previous calls. - Idempotence. A
configurethat reproduces the effective configuration MUST NOT change anything in the engine’s state beyond the interruption of rule 2 (§7): no reallocation, no loss of tables. When the configuration changes, only the modified options take effect, and each affects only what it governs. - Atomicity. The engine validates all values, then applies all of them or none. On failure, the previous configuration remains in force.
- Acknowledgment. The engine emits the
doneonce the configuration has been applied. - Errors. An unknown option or an out-of-domain value causes
invalid, with apathpointing to the option.
Option schema.
type |
Descriptor | Constraints |
|---|---|---|
bool |
default |
— |
int |
default, min, max |
min ≤ default ≤ max |
string |
default, values (optional) |
If values is present, it is non-empty and contains default, and every configured value MUST belong to it |
Every descriptor MAY carry about, a string intended for humans. Option names SHOULD be in snake_case. Two names are reserved, and an engine that exposes these notions MUST use them:
threads(int): number of search threads;hash(int, in MiB): size of the transposition tables.
8.4 search
→ {"id":3,"op":"search",
"rules":"sashite.sanki.kernel/1",
"position":"4k^3/8/8/8/8/8/8/R3K^3 / W/w",
"moves":["a1-a4","e8-d8"],
"clock":{"own":{"deadline":59000,"remaining":60000,"inc":1000},
"opp":{"remaining":58000,"inc":1000},
"overhead":400}}
Fields
| Field | Required | Content |
|---|---|---|
rules |
Yes | Announced rules identifier |
position |
Yes | FEEN |
moves |
No ([]) |
PMN Moves played from position |
counters |
No ({"halfmove":0,"ply":1}) |
Counters at position |
clock |
No | Clock |
limits |
No | Caps |
fresh |
No (false) |
Answer as a fresh process would |
- Rules. An unannounced identifier causes
unsupported. - Position. A malformed FEEN causes
invalid. A pairing the engine does not support causesunsupported. A FEEN whose geometry is not that of the rules, that is not canonical for the rules document, or that is illegal, causesillegal. - Moves. The Moves in
movesMUST be canonical PMN (§3). A malformed Move, or a drop that does not name its Piece, causesinvalid, with apathpointing to it. A Move that is illegal, or whose PMN is not the canonical one, causesillegal, with apathpointing to it. A Move played after a terminal position is illegal. - History. The host SHOULD send the Initial Position of the game together with the full history. A host that sends less accepts that the engine cannot judge the endings that depend on history, such as repetition.
- Counters.
halfmove(at least 0) counts the half-moves since the last irreversible Move.ply(at least 1) is the number of the next half-move fromposition. Each absent field takes its default, and an absentcountersis equivalent to its default, including for the recognition of a continuation. - Multiple defects. If a request has several defects, the engine reports only one of them: an
invalidbefore anunsupported, and anunsupportedbefore anillegal.
Clock
| Field | Required | Content |
|---|---|---|
own |
Yes | Side of the Active Player of the searched position |
opp |
No | Side of the other player |
overhead |
No (0) | Latency outside the search: from the emission of the request to its receipt, and from the emission of done until the player’s clock actually stops (transmission, validation, publication…) |
A side describes the current period of the player’s clock, and what follows it.
| Side field | Required | Domain | Meaning |
|---|---|---|---|
deadline |
own: yes; opp: forbidden |
≥ 0 | Time left, at the emission of the request, before the player loses on time — or less, if the host wishes to cap the search. The engine’s share of it is deadline − overhead, counted from receipt (§Stopping) |
remaining |
Yes | ≥ 0 | Budget left in the current period |
inc |
No (0) | ≥ 0 | Time granted for each Move of the current period, whether credited before or after the Move |
togo |
No | ≥ 1 | Moves remaining in the current period, including the current one; present if and only if the period has a quota of Moves |
time |
With togo |
≥ 0 | Nominal duration of the current period: the budget it grants each time it begins |
carry |
No (true) |
Boolean | Whether the budget left when the quota is reached is carried into the next period (true) or discarded (false); requires togo |
next |
No ([]) |
Array | The periods that follow the current one, in order; each an object {"time","inc","moves","carry"} with the meanings above: time required, inc optional (0), moves (≥ 1) optional, carry only with moves (true) |
The host measures all durations at the moment of sending, according to the time rules it applies, and folds into remaining the time already charged to the player for this Move. The engine counts them down from receipt. deadline protects against losing on time; the other side fields serve to plan the use of time, under the following model.
Periods. After the Move that reaches the quota of a period, the clock enters the first period of next — or the current period again, if next is empty — with that period’s time, plus the budget left if carry. When a period without a quota is exhausted, the overspend is charged to the next period, and the player loses on time when no period remains. A period with a quota cannot be overspent: exceeding its budget loses on time. A host whose time rules differ maps them onto these fields as closely as it can, and deadline remains exact: an engine that respects deadline never loses on time, whatever the planning fields say.
Informative. Mapping of common time controls, for the Active Player’s side:
| Time control | Expression |
|---|---|
| Fixed budget, or Fischer with the increment credited after the Move | remaining, inc; deadline = remaining |
| Fischer with the increment credited before the Move, Bronstein or US delay | remaining, inc (the delay); deadline = remaining + inc |
| Time per Move (Sanki byōyomi) | time = 0, remaining = 0, inc, togo = 1, carry: false; deadline = inc |
| Japanese byōyomi with n periods left | as above; deadline = inc, or up to n × inc if the host lets the engine spend periods, which the planning fields do not model |
| Canadian (n Moves per period) | time, remaining, togo, carry: false; deadline = remaining |
| Classical, 40 Moves then a second period | time, remaining, inc, togo, carry: true, next = [{"time": second, "inc": inc}]; deadline = remaining |
| Main budget then overtime per Move | remaining, next = [{"time": 0, "inc": per_move, "moves": 1, "carry": false}]; deadline = remaining + per_move |
Limits
limits MUST be non-empty.
| Field | Domain | Cap |
|---|---|---|
depth |
≥ 1 | Depth, in the sense of the engine’s algorithm |
nodes |
≥ 1 | Amount of work since receipt, in engine-specific units (positions visited, simulations…) |
movetime |
≥ 0 | Duration from receipt; overhead does not apply to it. It is a maximum, and exact in the absence of clock, subject to the exceptions of §Stopping |
mate |
≥ 1 | Mate by the Active Player, proven by an exact mate score of k half-moves, with k ≤ mate |
Informative. nodes is the most portable limit across engines. On its own, mate may never be reached: the host SHOULD combine it with another limit. A mate delivered by the Active Player always takes an odd number of half-moves.
Stopping
- Limits. Limits are hard caps: the search stops as soon as one of them is reached. A continuation (§State and reproducibility) that has already reached a cap on
depthormateanswers at once. - Clock. The engine MUST emit the
doneno later thanown.deadline − overheadafter receipt, its own stopping latency included. If that bound is not positive, it does not apply: the engine answers at once, without searching, and only the bounded stop below binds it. The engine plans its use of time from the other side fields. - Both together. The search stops at the first deadline reached, whether it comes from a limit or from the clock.
- Without a clock. The engine MUST NOT stop before a limit is reached, except on a terminal position, with a single legal Move, or if its result is proven final.
- Infinite search. Without
clockorlimits, the search ends only with the next request other thanping. - Terminal position. The engine answers immediately, with
bestset tonull. - Single Move. With a single legal Move, or a single root (§10), the engine MAY answer immediately.
- Bounded stop. The engine SHOULD emit the
donewithin 50 ms after a limit is reached or an interrupting request is received. - Overrun. An engine whose
donehas not arrived, after the emission of the request, within the larger ofown.deadlineandoverheadplus a grace period, withinmovetimeplus a grace period, or within the grace period that follows an interrupting request, has failed. The host then plays its fallback (§11) and SHOULD close the session.
Guaranteed result
- Once validated, a search always ends with a
done, or else the session ends. - Outside terminal positions,
bestMUST be a legal Move, even if the search is interrupted before the end of its first iteration. - A recoverable failure during the search is treated as an interruption. The engine emits the
donewith the best available result, at worst a legal Move without variations, and describes the failure on its standard error. If it can no longer provide a legal Move, the failure is fatal. - Safety net. The engine SHOULD emit an
infocarrying a variation as soon as it holds a legal Move, and before any deep search: a host deprived of thedonethen has a Move to fall back on (§11).
For an engine that honors the bounded stop, the host therefore always obtains a legal Move within a bounded time by sending cancel.
State and reproducibility
- Reuse. Without
fresh, the engine MAY reuse its internal state (tables, tree), and the result MAY depend on previous searches. Reuse MUST NEVER affect the validity of the result. - Continuation. When a search carries the same
rules,position,counters,moves, and the same value of every field a feature adds tosearch(lines,strength,roots…) as the previous search, when neither hasfresh: true, and when noconfigurein between changed the configuration, the engine SHOULD treat it as a continuation and resume its tree and iterations. The new search proceeds as if it started at its receipt: its durations and itsnodescount from there. - Freshness. With
fresh: true, the engine MUST answer as a fresh process would after receiving the last successfulconfigure. A host SHOULD sendfresh: trueon the first search of a game. - Reproducibility. With
fresh: true,threadsset to 1, limits restricted todepthornodes, and neitherclocknorstrength, the same request SHOULD produce the samedone.
Informative. To think on the opponent’s time, the host starts an infinite search on the predicted position, obtained by playing the second Move of the first variation. It then sends the real search once the opponent has moved: if the prediction was right, it is a continuation. For the continuation to be recognized, the host SHOULD keep a stable representation: the same starting position, and the full history in moves.
8.5 cancel
→ {"id":7,"op":"cancel"}
← {"re":3,"ev":"done","best":"a4-a7"}
← {"re":7,"ev":"done"}
cancel has no effect other than the interruption common to every request (§7).
9. Events
9.1 Variations and scores
These definitions apply to info and done alike.
Variation. A variation is an object {"pv", "score"}. pv is a legal, non-empty sequence of canonical PMN Moves from the searched position; score is its evaluation.
List of variations. A list of variations is sorted from best to worst, and the first Moves of its variations are distinct. It contains at most search.lines variations (only one without the lines feature), and never more than the number of legal Moves (or of search.roots).
Score. A score contains exactly one of the following fields:
| Field | Content |
|---|---|
cp |
Evaluation on the engine’s own scale, positive if the Active Player has the advantage; an engine SHOULD scale it so that 100 is roughly the value of the game’s least valuable Piece. Its magnitude is meaningful for a given engine, but not comparable across engines |
mate |
Non-zero integer, in half-moves; positive if the Active Player mates, negative if the Active Player is mated |
Two optional fields complement it:
| Field | Content |
|---|---|
bound |
"lower" or "upper", when the score is only a bound |
wdl |
Three integers between 0 and 1000 (win, draw, loss), summing to 1000; a common scale, with an engine-specific calibration |
Every score is expressed from the Active Player’s point of view. A positive mate score implies wdl = [1000, 0, 0], and a negative mate score implies [0, 0, 1000].
Informative. wdl is the only score a host can compare across engines; a host that decides on scores — to resign, to offer a draw — SHOULD decide on wdl and mate, never on cp.
9.2 info
info is a provisional result. It is entirely optional: an engine MAY emit none, and MAY drop any of them. Each field of an info supersedes the same field of the previous info.
← {"re":3,"ev":"info","depth":12,"seldepth":19,
"nodes":84211,"elapsed":40,
"variations":[{"pv":["a4-a7","d8-e8","e1-e2"],
"score":{"mate":17,"wdl":[1000,0,0]}}]}
| Field | Domain | Content |
|---|---|---|
depth |
≥ 1 | Last completed iteration, in the sense of the engine’s algorithm |
seldepth |
≥ 1 | Selective depth, in the sense of the engine’s algorithm |
nodes |
≥ 0 | Work done since receipt |
elapsed |
≥ 0 | Time elapsed since receipt |
hashfull |
0 to 1000 | Table occupancy, in per mille |
variations |
— | Complete list of current variations (§9.1) |
An info contains at least one field. The engine SHOULD limit the rate to about ten info per second, the safety net of §8.4 excepted.
9.3 done
For ping, configure, and cancel, done is empty. For hello, see §8.1. For search, done carries the final result:
← {"re":3,"ev":"done","best":"a4-a7","depth":21,"nodes":1804211,"elapsed":2310,
"variations":[{"pv":["a4-a7","d8-e8","e1-e2"],
"score":{"mate":17,"wdl":[1000,0,0]}}]}
| Field | Content |
|---|---|
best |
Legal Move in PMN; null if and only if the searched position is terminal |
variations |
List of final variations (§9.1), present as soon as the engine has at least one evaluation; the first one begins with best |
depth, seldepth, nodes, elapsed, hashfull |
As in info, optional: the state of the search at its end |
- The engine SHOULD provide
wdlin each variation if it has a win-draw-loss model. - The opponent’s expected reply is the second Move of the first variation, if any.
- Every PMN emitted by the engine MUST be canonical (§3).
bestMAY be"..."(Pass Move) if the rules allow it.
9.4 error
← {"re":8,"ev":"error","code":"illegal","path":"/moves/0","message":"…"}
| Code | Meaning |
|---|---|
invalid |
Request malformed, out of domain, or not negotiated; any envelope error (§6.5) |
illegal |
Illegal or non-canonical position or Move — a drop without its Piece excepted, which is invalid (§8.4) |
unsupported |
Request well-formed, but naming rules or a pairing the engine does not implement |
internal |
Engine failure: with re, the request failed for an internal cause before its search started; without re, the failure is fatal |
path(JSON Pointer) andmessageare optional.messageis intended for humans, in the language chosen by the engine. The host MUST NOT depend on it; for localized display, it builds its own text fromcodeandpath.- Fatality depends only on the presence of
re(§7). - A validated search never ends with an attached error (§8.4).
Informative. Action expected from the host for each attached code:
| Code | Host action |
|---|---|
invalid |
Fix the request |
illegal |
Check the data against the rules document; on data the host has validated itself, conclude that the rules diverge |
unsupported |
Use another engine for this game |
internal |
Retry once, then restart the engine |
9.5 Unknown values
| Unknown value | Host fallback |
|---|---|
ev |
Event ignored |
code |
internal |
bound |
Bound ignored |
Option type |
Option left at its default value |
10. Features
The engine announces its features in the done of hello. The host MUST NOT use a feature that has not been announced.
| Feature | Parameters | Adds |
|---|---|---|
lines |
max |
search.lines: integer between 1 and max (default 1) |
strength |
min, max |
search.strength: {"elo":n}, with n between min and max |
roots |
— | search.roots: non-empty array of distinct Moves of the searched position, which restricts the search to them; best and the first Move of every variation belong to it. Its Moves are validated like moves (§8.4); an empty array, or a repeated Move, causes invalid. A host does not send roots on a terminal position, where no Move is legal |
advice |
— | done.advice: "resign" or "draw", when the engine judges its position lost, or judges a draw the best outcome; absent otherwise. It binds the host to nothing |
x_<vendor>_<name> |
Free | Defined by the vendor |
Informative. roots serves analysis (the exclusion of a book Move, the verification of a puzzle) and the simplest engines: a host that passes every legal Move lets an engine without a rules library play.
11. Safety
Engine.
- It validates positions and Moves.
- It MUST check that the geometry of a FEEN matches the rules before any allocation proportional to its size; otherwise, it answers
illegal. - It trusts the host for options, which may designate paths or set allocations.
- It needs no argument, environment variable, or network access to speak SEI (§5).
Host.
- It MUST validate, with its own rules, every Move it plays or publishes.
- Fallback. When the engine fails during a search (§8.4 Overrun, §5 Unexpected end), or emits a
donethe host cannot validate, the host plays the first Move of the latestvariationsit received, after validating it, or else a legal Move of its own choice. A host SHOULD make that Move unpredictable, and MUST log every fallback. - It MUST escape every string coming from the engine before displaying it.
- It MUST NOT configure any resource option (
threads,hash…) beyond its own budget, whatever the announced bound. - It SHOULD treat any protocol violation as an engine failure.
- It launches an engine it does not trust as §5 says: another user or a sandbox, bounded resources.
Untrusted users. The host MUST NOT let them set options, and SHOULD bound the searches it relays on their behalf.
12. Conformance
Conforming engine. It MUST:
- implement
hello,ping,configure,search, andcancel; - accept all core fields;
- comply with §5 to §7, exit codes included;
- end every validated search with a
done, within its budget; - produce canonical output.
Conforming host. It MUST:
- send only
helloorpingbefore a successfulhello; - emit strictly increasing
idvalues; - read the engine’s outputs as required by §5;
- validate Moves and escape the engine’s strings;
- tolerate unknown fields and values;
- play a fallback, and never a Move it has not validated.
Conformance vectors and probe hosts and engines are described in the implementation guide (§15).
13. Evolution
13.1 Numbering
The specification follows SemVer.
- Major. The major version is the protocol version, the only one negotiated in
hello. - Minor. A minor version only adds standard features, or event fields and values.
- Patch. A patch version only affects wording, or resolves an ambiguity (§13.3).
13.2 Additivity
- Before any use. As long as no request of the session has carried the fields or operations of a feature, behavior is exactly that of the base version.
- After use. Once used, the feature itself defines its interaction with each core request, for the requests that carry its fields. A feature that adds only event fields, such as
advice, is in use from its announcement. - Untouchable invariants. No feature alters the permanent core (§6.6), rules 3 to 7 of §7, the legality of
bestand of everypv(§8.4, §9.1), or §13.4. A feature MAY relax rule 2 of §7 or the stopping rules of §8.4, only within the limits it defines. Any change to the untouchable invariants requires a new major version.
13.3 Stability
- Nothing removed. Within a major version, nothing is removed or modified.
- Deprecation. A deprecated feature may still be announced by engines, but hosts SHOULD stop using it. It is removed only in the next major version.
- Frozen codes. The error codes of core requests are frozen. A new code can only concern the fields of a feature.
- Errata. A patch or minor version MAY resolve an ambiguity. If the clarification would make non-conforming an implementation that followed a reasonable reading of the previous text, it waits for the next major version, or takes the form of a feature.
13.4 Future events
An event type defined after version 1.0 is never terminal.
13.5 Standard options
Reserved option names are frozen at version 1.0 (threads, hash). Any later standard option goes through a feature, whose announcement guarantees that the engine honors its meaning.
13.6 Vendors
Vendor-specific extensions, whether features or event fields, are named x_<vendor>_<name>. The vendor SHOULD choose for <vendor> a name it controls, such as a domain or project name.
14. Example session (informative)
→ {"id":0,"op":"ping"}
← {"re":0,"ev":"done"}
→ {"id":1,"op":"hello","versions":[1]}
← {"re":1,"ev":"done","version":1,"versions":[1], …}
→ {"id":2,"op":"configure","options":{"threads":4,"hash":256}}
← {"re":2,"ev":"done"}
→ {"id":3,"op":"search",
"rules":"sashite.sanki.kernel/1",
"position":"4k^3/8/8/8/8/8/8/R3K^3 / W/w",
"moves":["a1-a4","e8-d8"],"fresh":true,
"clock":{"own":{"deadline":60000,"remaining":60000,"inc":1000},"overhead":400}}
← {"re":3,"ev":"info","variations":[{"pv":["e1-e2"],"score":{"cp":0}}]}
→ {"id":4,"op":"ping"}
← {"re":4,"ev":"done"}
← {"re":3,"ev":"info","depth":12,"nodes":84211,
"variations":[{"pv":["a4-a7","d8-e8"],"score":{"mate":17}}]}
← {"re":3,"ev":"done","best":"a4-a7",
"variations":[{"pv":["a4-a7","d8-e8","e1-e2"],"score":{"mate":17}}]}
The first info is the safety net: the first legal Move the engine found. The host plays a4-a7 and lets the engine think on d8-e8. It then sends the real search, which is a continuation:
→ {"id":5,"op":"search","rules":"…","position":"…",
"moves":["a1-a4","e8-d8","a4-a7","d8-e8"]}
→ {"id":6,"op":"search","rules":"…","position":"…",
"moves":["a1-a4","e8-d8","a4-a7","d8-e8"],
"clock":{…}}
← {"re":5,"ev":"done","best":"e1-e2", …}
← {"re":6,"ev":"done","best":"e1-e2", …}
An illegal Move, then the end of the session:
→ {"id":7,"op":"search",
"rules":"sashite.sanki.kernel/1",
"position":"4k^3/8/8/8/8/8/8/R3K^3 / W/w",
"moves":["a1-h8"]}
← {"re":7,"ev":"error","code":"illegal","path":"/moves/0"}
The host then closes the engine’s standard input, and the engine exits with code 0.
15. Companion documents (informative)
Three documents complement this specification. Each states the version of SEI it covers.
- SEI — Rules registry: the rules identifiers of Sashité’s games, and for each the rules document it designates:
sashite.sanki.kernel/1(Sanki: the Kernel, its module digests, the canonical FEEN and PMN of every Sanki Move, the lettersW,J,Cand their nine pairings). - SEI — Mapping to UCI: adapters A and B, notation conversions, and emulation costs.
- SEI — Implementation guide: reference tooling, conformance vectors, probe hosts and engines, the two-thread architecture, crash recovery, isolation of untrusted engines, protocol detection, and the minimal engine.
16. Rationale (informative)
- A process, not a library. An engine linked into the host is trusted code: it shares the host’s memory and secrets, a panic can abort the host, and a search that never returns cannot be stopped. A process can be killed, bounded, relaunched, run as another user and written in any language; the host’s failure domain ends at a pipe.
- Standard streams, not a socket. No port, no address, no network: the channel exists exactly as long as the child, and the engine needs no network at all, which lets a deployment deny it.
- JSON Lines, not a line grammar. Every language has a strict JSON parser, and two parsers never read one line two ways. The price is that a session cannot be typed by hand; a small command-line tool restores that.
- Requests and events correlated by
id. UCI pairsgowith the nextbestmoveby position in the stream, which breaks as soon as messages interleave. An explicitidmakes every event attributable, letspingbe answered in every state, and lets a single rule — the presence ofre— say whether an error is fatal. - Self-contained searches, not incremental state. UCI’s
position … moves …anducinewgamelet the engine’s view drift from the host’s, and make a restart a replay. A search that carries everything makes a restart free and a desynchronization impossible, for a few kilobytes at most. A continuation is recognized by equality of requests, which replacesponderandponderhitwithout a mode. - A complete configuration, not options one by one.
configuredeclares the whole configuration, so that its effect never depends on the order or the number of previous calls, and an unchanged configuration costs nothing. - The engine validates, and the host validates too. An alternative was considered in which the host lists every legal Move and the engine answers with an index: an index cannot be illegal, and the host never publishes text the engine wrote. But it ties the protocol to a host that owns a rules oracle for every game it hosts, and gives an engine that searches deeper than one Move nothing it did not need its own rules for. SEI keeps the safety property where it belongs — the host MUST validate every Move it plays (§11) — makes canonical PMN a checkable property through the rules document (§3), and recovers the index model, for engines without a rules library, with
roots(§10). - One promise about time.
deadlineis the only field an engine must honor; the planning fields inform it and may be approximate.overheadcovers everything outside the search, in both directions, so that a host measures once and an engine counts from receipt. - Progress may be lost.
infomay be dropped, so that a slow reader never stalls a search; the safety-netinfoand the host’s fallback (§11) give the host a Move even when thedonenever comes. - No draw or resignation acts. Whether a program may end a game is its operator’s decision, not the engine’s;
advicelets the engine speak without deciding. - Not UCI, USI, or UCCI. None of them speaks FEEN, a game whose two players use different styles, or drops without an adapter; each is tied to one game. SEI is tied to none, and a mapping to UCI exists for legacy engines (§15).
- Not in this version: several games per process, a stop reason in
done, feedback at the end of a game for learning engines, hints and analysis modes. Each is a feature if it earns one (§13).
17. License
This specification is made available under the terms of the Open Web Foundation Agreement 1.0 (OWFa 1.0).
The authoritative legal text is the OWF “Final Specification Agreement (OWFa 1.0)”.
