A local MCP server handing a model the reckoner dictionary, and the grounding to use it: mills/sparky
The same calculator halifax puts at a terminal, handed instead to a language model (an AI assistant) as callable tools. A student asks a question in English. The model answers it by computing, and it shows its working. Every word in the language carries its own stack effect and description, so the model never has to guess what a word does.
Named after Karen Spärck Jones (1935–2007). She gave information retrieval inverse document frequency (a way of scoring how telling a search word is) and spent a career arguing that computing was too important to be left to men. The name is apt twice over. Her subject was retrieving the right material for a question, and half this server's job is retrieving the right grounding for a word.
bin/mill run mills/sparky/sparky.shoddy # the same dictionary, at a terminal
dotnet run --project hosts/mcp/sparky # the server, on stdin and stdout
It folds thirty-eight of the reckoner's forty-one seeds. The three it leaves out are left out whole. Their words are not in the dictionary at all, rather than present and answering nonsense; see what is deliberately absent.
Sparky is a local stdio server speaking MCP, the Model Context Protocol, the standard way an assistant launches a tool and talks to it. A client launches Sparky as a child process: a program started and owned by another program. The two speak JSON-RPC (messages written as structured text), one message per line, on stdin and stdout, the input and output streams every program has. Nothing listens on a network socket. So every client needs the same two things: the command to run, and its arguments:
sparky --root PATH
--root is where the session's files live: sparkyrc,
anything save writes, any chart PLOTSAVE writes. Omit
it and the server uses LocalApplicationData/Shoddy/Sparky, creating
it if it is not there. Name a root that does not exist and the server refuses
to start, saying which path it could not find. A root you named is one you
meant, so a typo is reported rather than materialised as a new folder.
Get the executable from the
releases page. Every
release carries one sparky archive per OS (.zip for Windows,
.tar.gz for Linux and macOS), each holding a single self-contained
executable: a program that needs nothing else installed. Download, unpack,
done. No repository, no .NET install, nothing else on the machine. The
binaries are unsigned (they carry no publisher's certificate), so the first
launch meets Windows SmartScreen (“More info”, then “Run
anyway”) or macOS Gatekeeper
(xattr -d com.apple.quarantine sparky). That happens once per
download.
Or build it from the repo, which is the path for anyone changing it:
./build.ps1 # repo root: publishes bin/mill (./build.sh elsewhere)
cd hosts/mcp
./build.ps1 release # sparky lands in artifacts/bin/sparky/release/
./build.ps1 publish # or the per-OS release archives, in artifacts/publish/
If you build from the repo, register a copy of the executable from
somewhere outside the tree, not its artifacts/ path. Here is
why. A client keeps the server running for its whole session, and a running
server holds its files open. artifacts/ is exactly what the root
build's clean deletes. So a registration pointing into the build
tree makes every full rebuild collide with every open session.
Client configuration formats move faster than this page does. The contract above does not. If a form below has drifted, check that client's own documentation and give it the same command and arguments. All four forms are the same two facts under different keys.
Claude Desktop: claude_desktop_config.json
(%APPDATA%\Claude\ on Windows,
~/Library/Application Support/Claude/ on macOS):
{
"mcpServers": {
"sparky": {
"command": "C:\\shoddy\\artifacts\\bin\\sparky\\release\\sparky.exe",
"args": ["--root", "C:\\Users\\you\\Documents\\sparky"]
}
}
}Claude Code: one command, no file to edit:
claude mcp add --scope user sparky -- /path/to/sparky--scope user is deliberate. It registers sparky once, at the
top level of the user's configuration, for every folder and every surface
Claude Code has, the terminal and the VS Code panel alike. The default scope
files the server under the one project directory the command was typed in.
That is the wrong home for a tool that is not about any project.
If the model's file tool is Fettler,
give sparky a --root that Fettler declares as a tree, and the two
can hand each other a file: Fettler writes data.json there, and
the session runs "data.json" JSONLOAD. A directory of its own,
outside any repository, is the right shape; the repository itself is not,
because tapes, saved words and PNGs would land where git status
reports them and a session's WRITELINES could reach source.
Declare it in .fettler.local.json with create and
update, and name the same directory after --root in
the registration above.
VS Code (Copilot): the user-level mcp.json:
Command Palette → MCP: Open User Configuration. A workspace can
carry the same entry in .vscode/mcp.json instead. But a server
that serves every folder belongs in the user file:
{
"servers": {
"sparky": {
"type": "stdio",
"command": "/path/to/sparky"
}
}
}Anything else: a client that speaks MCP over stdio needs no adapter. Launch the executable, write one JSON-RPC message per line to its stdin, and read one per line from its stdout. Diagnostics go to stderr (the separate stream for error messages) and never to stdout, so nothing the server logs can corrupt the stream.
Two lines on stdin are enough to see the whole handshake, the opening exchange in which the two programs introduce themselves:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"eval","arguments":{"lines":["{ 12500 13100 11900 } MEAN"]}}}' \
| sparky --root /tmp/sparkyThe second answer contains [stack] x: 12500. If it does, the
dictionary is live. The client's only remaining job is to launch the same
command.
The server serves this grounding itself, as the two resources
sparky://grounding/reckoner and
sparky://grounding/surface, so a client that reads resources needs
nothing from this section. What follows is those two, concatenated and
reproduced verbatim, for the case that has no server to ask: a system prompt, a
project instruction file, or a model you are briefing before it ever connects.
A test asserts this block and the served text are the same, so the copy below
cannot drift from the one a client is given.
Two parts of it justify their length. The first is the instruction not to invent a word. Every word carries its own stack effect and description, read from the running engine, so a model that asks is never wrong, and a model that guesses is fluently, confidently wrong. The second is Where the calculator cannot catch you. Everything else a model gets wrong is met with a refusal that names the fix: wrong arity (the number of arguments a word takes), wrong type, wrong order, all caught. So the briefing can afford to be short about them. Those four are the ones that return a plausible number instead.
# The reckoner: how a line works
Lines are RPN (reverse Polish notation). Numbers and strings go on the
stack, and words work on what is there. `3 4 +` leaves 7. The stack is
shown after every line, with the top value labelled `x`, then `y`, `z`
and `t` beneath it.
- **A list is `{ 1 2 3 }`.** A program (a quotation) is `[ DUP * ]`.
`{ 1 2 3 } [ 2 * ] MAP` answers `{ 2 4 6 }`.
- **A string is double-quoted.** It shows with its quotes on the stack
and without them under `PRINT`.
- **You define a word with `: NAME ... ;`**, for example
`: VAT DUP 0.2 * + ;`. It may span several lines. The definition is
not run until it closes.
- **The line is the transaction.** A line either takes effect whole or
is refused whole, and a refusal leaves the stack exactly as it was.
Refusals start with `?:`.
- **Nothing you can type ends the session.** If something is wrong, you
get a refusal and another chance. Trying a line costs nothing, so try
it rather than asking.
- **`UNDO` restores a stack, not the world.** A file already written is
still written. A line already drawn is still drawn. A `PRINT` already
printed cannot be unprinted.
## Two rules that differ from other stack languages
- **A user word takes exactly one cell.** Not "as many as it pops": one.
If a word needs a second argument, store it in a register first with
`STO` and read it inside with `RCL`. A two-argument definition is the
most common mistake here. The definition is accepted, and every call
to it is refused. The refusal names the word inside it, such as
`+ needs 2, the stack holds 1`, not the word you defined.
- **A word cannot call itself.** A definition is checked against the
dictionary as it stands, so its body may name only words that already
exist, never the name being defined. Loop with `TIMES`, `MAP`,
`FILTER` or `FOLD` instead.
## Reading a stack effect
`HELP` answers one. `STO ( x name -- )` means the word takes two cells
and leaves none. The order is the order you push them: the value first,
then the name. `RCL ( name -- x )` takes one and leaves one.
`PLOTHISTOGRAM ( plot xs bins -- )` takes three, in that order.
Argument order is the mistake you will make most often. Reading the
effect line prevents it.
## Never invent a word
`WORDS` lists everything, grouped by the seed it came from. `HELP NAME`
gives a word's exact stack effect and description. `VIEW NAME` shows a
definition you made. Use the `help` and `words` tools rather than
guessing that a word exists. Every word carries its own effect and
description, so there is never a reason to invent one.
## A refused line tells you the fix
This is why trying costs nothing and why this briefing can be short. The
engine knows far more about its own words than any prompt can carry, and
it says so. Real refusals, verbatim:
12500 13100 11900 MEAN ?: MEAN needs a LIST, got NUMBER
{ 1 2 3 4 } 2 2 MAT ?: MAT needs a NUMBER, got LIST
640 480 PLOTOPEN ?: PLOTOPEN needs 3, the stack holds 2
{ 1 2 2 3 } 4 PLOTHISTOGRAM ?: PLOTHISTOGRAM needs 3, the stack holds 2
"3" 4 + ?: + is not defined for STRING and NUMBER
Each one names what was wrong. Send the corrected line. The stack is
exactly as it was. Do not apologise to the user for a refused line, and
do not describe it as a failure. It is how you find the right line.
## Where the calculator cannot catch you
Everything above is caught. These four are not: the line is accepted, an
answer appears, and it is the wrong answer. Read this section closely.
**Trig is in radians unless you say otherwise.**
90 SIN x: 0.8939966636 radians, and almost certainly not the question
DEG 90 SIN x: 1 degrees
Set `DEG` before any trigonometry a person asked for in degrees, and say
in your answer which mode you used. `RAD` switches back.
**Sample and population statistics are different words.**
{ 2 4 4 4 5 5 7 9 } STDDEV x: 2.138089935 sample, divides by n-1
{ 2 4 4 4 5 5 7 9 } STDDEVP x: 2 population, divides by n
The same goes for `VAR` and `VARP`. Both are right answers to different
questions, and nothing will tell you that you chose the wrong one.
Decide which the student meant, and say which you used.
**`FIX` changes what is shown, not what is held.**
2 FIX 3.14159265 x: 3.14
The stack still holds every digit, and `STD` shows them again. Never
read a `FIX`ed display back as the value.
**Numbers are binary floating point (IEEE doubles), so money needs its
own words.**
0.1 0.2 + x: 0.3
0.1 0.2 + 0.3 = x: False
The display rounds, but the comparison does not. For currency, use
`MONEY` and its words. They are exact, and they refuse a bare number so
you cannot mix the two by accident.
## A worked line or two
{ 12500 13100 11900 } MEAN
x: 12500
A word that needs two arguments, with one stored first:
0.2 "rate" STO
: WITHVAT DUP "rate" RCL * + ;
250 WITHVAT
x: 300
A matrix, built from its row and column counts first:
2 2 { 1 2 3 4 } MAT LINDET
x: -2
A chart. The third line sends back the picture:
640 480 "p" PLOTOPEN
"p" { 1 2 2 3 3 3 } 4 PLOTHISTOGRAM
"p" PLOTBLIT
# What this server is, and what it is not
Sparky gives you the Halifax dictionary as callable tools: the whole of
the reckoner's standard library at an RPN prompt. It also gives you the
grounding to use it correctly. It covers statistics, matrices, linear
algebra, linear and integer programming, finance, symbolic algebra,
neural nets, regular expressions, sparse matrices, CSV/JSON/XML/HTML,
indexed files, number bases and bit work, charts and turtle graphics,
geography, calendar dates, the sun, moon and planets, and sorting and
grouping lists by a key.
**Compute the answer. Do not estimate it.** That is the purpose of this
server: you have a calculator that shows its working, and a student is
better served by `{ 12500 13100 11900 } MEAN` than by your arithmetic.
## What is in the dictionary
Over eight hundred words, grouped by the seed each came from. This is the
index. `words` gives the full list, and `help` gives any one word exactly.
core arithmetic, comparison, stack shuffling, registers, UNDO
and REDO, TRACE, the tape, angle and display modes, SHAPE,
and the combinators MAP FILTER FOLD TIMES IFT IFTE
builtin string slicing and character codes, number parsing,
arrays, whole-file read and write, the clock
seed-math logs to a base, hypotenuse, distance, clamp, lerp, remap
seed-stats mean, median, spread, quantiles, correlation, normal, t
seed-seq ranges, length, reverse, concat, first and rest, sorting
seed-str case, trim, split, join, fixed-decimal text
seed-money exact decimal money, splitting a sum without losing a penny
seed-matrix building matrices, identity, transpose, multiply, dot
seed-dict string-keyed dictionaries, and DPATH through nested ones
seed-file line-oriented files, and whether one exists
seed-clock timestamps, monotonic ticks, elapsed time, durations
seed-random seeded generation, ranges, shuffling, sampling
seed-csv reading and writing CSV, columns, headers, filtering
seed-bool number bases, bit operations, masks, fields, logic gates
seed-shaker reversible obfuscation of a list, with a tamper checksum
seed-json parse, render, pretty-print, load and save JSON
seed-xml the same for XML
seed-html the same for HTML
seed-simplex linear programs, and MPS files
seed-lin linear algebra: determinants, inverses, decompositions,
eigenvalues, solving
seed-eng angles, complex numbers, statistics, number theory,
polynomials, calculus, units and physical constants
seed-fin interest, annuities, loans, NPV and IRR, depreciation,
day counts, bonds
seed-alg symbolic algebra: simplify, expand, factor, solve, and
first-order differential equations
seed-neural small neural networks: build, train, predict, score, save
seed-recio fixed-layout binary record files
seed-isam indexed files: keyed lookup, ranges, insert and update
seed-scribbler a pixel canvas to draw on
seed-net outbound HTTP fetches
seed-turtle turtle graphics
seed-plotter charts: histogram, scatter, box, bar, pie
seed-https HTTPS fetches, and reading a response apart
seed-regex regular expressions: test, find, groups, replace, split
seed-sparse sparse matrices, stored by column
seed-mip mixed-integer programs
seed-terminal PRINT
seed-geo distance, bearing and area on the globe, sunrise and sunset
seed-julian calendar dates, Julian day numbers, weekdays, date arithmetic
seed-ephemeris the sun, moon and planets: positions, phases, rising and setting
seed-sinq sorting and grouping by a key, distinct items, set operations,
batches
shell SAVE, LOAD, TAPESAVE, RESET and PUT, called as tools
Ask `subject` for any of those to get a teaching card: what the machine
is for, worked examples that run at this prompt, and its live word list.
## Resources are opened under a name, not held on the stack
A canvas, a plotter, a turtle, a record file and an indexed file are all
opened under a name you choose, and every later word takes that name:
640 480 "p" PLOTOPEN
"p" { 1 2 2 3 } 4 PLOTHISTOGRAM
"p" PLOTBLIT
Nothing is pushed by the open. `BOUND` lists what is open, `CLOSE` shuts
one by name, and `CLOSEALL` shuts them all. This is why `UNDO` cannot
leave a resource open with nothing referring to it: what is open is not
on the stack in the first place.
## What is deliberately absent
| Not here | Why |
|---|---|
| sound | the server does not control the host's audio and cannot know if anything is listening |
| key handling | there is no keystroke source, so the words that classify keys classify nothing |
| terminal escape sequences | there is no screen, and a client receiving them in JSON is worse off than one told the word is absent |
| any way to read input | no seed registers `INPUT`, `INPUTLINE` or `INKEY` |
| any listening socket | `NETGET` and `NETREQUEST` are the whole of the network surface. There is no listen, accept or blocking-wait word |
These are absent from the dictionary, not merely withheld. Ask `HELP`
about one of their words and you are told it is not a word here.
## Five things that catch a text-only caller harder than a person
- **A string on the stack is rendered after every line.** The stack is
shown whole with each answer, so a long string left at `y:` comes back
with every later line until something drops it, abbreviated to one
row but there every time. Bank what you are not using: `"doc" STO`,
and `"doc" RCL` when you want it. `SHAPE` says what a value is, how
long, which keys, without rendering it, and `DPATH` reaches a leaf in
one line.
- **`UNDO` does not un-draw.** After `TURTLEFORWARD`, `UNDO` restores
the stack and leaves the line on the canvas.
- **`UNDO` does not un-write, either.** A word that has already written
a file has still written it.
- **`abandon` costs a turtle more than a chart.** A turtle's position,
heading, pen and colour live in the session. A plotter has no state of
its own, so a chart is one word to redraw and a turtle drawing is not.
- **`CLOSE` ends the drawing.** After it, the dictionary cannot reach
the surface. This server keeps the last captured frame, though, so you
can still be shown what was made.
## Pictures
Charts and turtle drawings go into a pixel buffer with no window. A line
ending in `PLOTBLIT`, `SCRIBBLIT` or `TURTLEBLIT` carries its picture
back in the same answer. A drawing you forgot to blit is not lost: ask
the `canvas` tool, which reads the buffer as it stands.
## Persistence
`SAVE`, `LOAD`, `TAPESAVE`, `RESET` and `PUT` are the server's words, not
the dictionary's. Call the tools of those names. Typing them in a line
gets you a refusal saying so.
`put` is the way in for text a line cannot carry. A string in a line
ends at the first closing quote and has no escapes, so a JSON document,
which needs double quotes, or anything long, cannot be typed. Call the
`put` tool with a name and the text: it lands in a register exactly as
`"name" STO` would have put it, and the line that uses it is
`"doc" RCL JSONPARSE`.
Text already in a file under the root needs no `put`. A line reads it:
`"data.json" READFILE "doc" STO`, or `"data.json" JSONLOAD` for JSON.
The root is named in the server's `initialize` instructions and in the
`save`, `load` and `tape` tool descriptions, so you can tell which
directory a file tool must write to for this session to read it.
Files live under one root the server owns. A plain name like
`"mine.sparky"` lands in it, and so does `"saved/mine.sparky"`. A path
that tries to leave it, such as `"../elsewhere"` or an absolute path
somewhere else, touches nothing outside the root. A read of such a path
is refused with `CANNOT READ ... (OUTSIDE THE FILE ROOT)`. A write, a
delete or `FILEEXISTS` answers `False` instead, with no refusal, so check
what a write answers. The `save`, `load` and `tape` tools keep to the
same root and refuse a path outside it. Use plain names.
Thirty-eight seeds and the shell's own words, as WORDS lists
them, grouped by the seed each came from, in the order the dictionary was
built. This is an inventory, for choosing what to reach for. It is
not the reference. The running dictionary is, and help reads from
it, so where this page and the server disagree the server is right.
-- core --
+ - * / ^ MOD WRAP ABS SGN FLOOR CEIL ROUND MIN MAX SQRT EXP LN LOG
PI E = <> < > <= >= NOT AND OR TRUE FALSE DUP DROP SWAP OVER NIP
TUCK ROT ROLL PICK DEPTH CLEAR GATHER UNPACK LASTX STO RCL CLOSE
CLOSEALL BOUND UNDO REDO TRACE NOTRACE TAPE CLEARTAPE DEG RAD FIX
SCI ENG STD SIN COS TAN ASIN ACOS ATAN ATAN2 IFT IFTE TIMES MAP
FILTER FOLD HELP VIEW WORDS
-- builtin --
LEN LEFT RIGHT MID INSTR INSTRFROM CHR ASC CODES FROMCODES CODEAT
STR VAL VALOR ISNUMERIC NEGATE ATN ATN2 TANH ERF GAMMAP BETAI NTH
SETNTH ISEMPTY PREPEND DIM READFILE WRITEFILE APPENDFILE DELETEFILE
NETALLOWED ARGS CLOCK SLEEP CALL ERROR ASSERT
-- seed-math --
LOGB LOG2 HYPOT DIST CLAMP LERP REMAP ROUNDTO
-- seed-stats --
MEAN MEDIAN STDDEV STDDEVP VAR SUM QUANTILE CORREL NORMCDF NORMINV
TCDF
-- seed-seq --
RANGE LENGTH REVERSE CONCAT FIRST REST SORTL PAIR HEAD TAIL
-- seed-str --
STRCAT UPPER LOWER TRIM SPLIT JOIN TOFIXED
-- seed-money --
MONEY MADD MSUB MSPLIT MFMT
-- seed-matrix --
MAT IDENT TRANSP MMUL MATADD MGET DOT
-- seed-dict --
DHAS DGET DPUT DKEYS
-- seed-file --
READLINES WRITELINES APPENDLINE FILEEXISTS
-- seed-clock --
STAMP STAMPDATE STAMPTIME TICKS ELAPSED FORMATDURATION
-- seed-random --
RANDOM RANDOMRANGE RANDOMINT SHUFFLE SAMPLE SEED
-- seed-csv --
CSVLOAD CSVREAD CSVSAVE CSVWIDTH CSVHEADS CSVBODY CSVCOLUMN
CSVCOLUMNNUMS CSVGET CSVWHERE CSVPAIRS
-- seed-bool --
HEX BIN INBASE FROMBASE BITAND BITOR BITXOR BITNOT SHL SHR MASK
GETFIELD BITAT POPCOUNT NAND NOR XOR XNOR IMPLIES IFF ROTL ROTR SHRA
SIGNED UNSIGNED SIGNEXTEND NEGW ADDW SUBW MULW CARRIES OVERFLOWS
CARD CARDWITH LOWBIT HIGHBIT GRAY FROMGRAY PARITY PARITYBIT HAMMING
BCD FROMBCD MINTERMS IMPLICANTS TABLE
-- seed-shaker --
SHAKE UNSHAKE UNSHAKEOR SHAKEOK SHAKEMOD
-- seed-json --
JSONPARSE JSONTEXT JSONPRETTY JSONLOAD JSONSAVE
-- seed-xml --
XMLPARSE XMLTEXT XMLPRETTY XMLLOAD XMLSAVE
-- seed-html --
HTMLPARSE HTMLTEXT HTMLPRETTY HTMLLOAD HTMLSAVE
-- seed-simplex --
LPPROBLEM LPSOLVE MPSPARSE MPSPARSEZERO MPSLOAD MPSLOADZERO MPSNAMES
-- seed-lin --
LINTRACE LINNORM LINMAXABS LINISSQUARE LINISSYM LINREF LINRREF
LINRANK LINDET LINADJ LININV LINCHOL LINLUOF LINQROF LINCHARPOLY
LINEIGVALS LINEIGSYM LINSWAPROWS LINSCALEROW LINSCALECOL LINCOLS
LINAUG LINSOLVE LINPOW LINVNORM LINVUNIT LINCROSS
-- seed-eng --
ENGRAD ENGDEG ENGGRADOF ENGFROMGRAD ENGDMSOF ENGFROMDMS ENGSEC
ENGCSC ENGCOT ENGASEC ENGACSC ENGACOT ENGANGLEOF ENGHYPOT ENGDISTOF
ENGRECTX ENGRECTY ENGPOLAROF ENGSINH ENGCOSH ENGSECH ENGCSCH ENGCOTH
ENGASINH ENGACOSH ENGATANH ENGASECH ENGACSCH ENGACOTH ENGLOGBASE
ENGLOG2 ENGLOG1P ENGEXPM1 ENGNTHROOT ENGCADD ENGCSUB ENGCMUL ENGCDIV
ENGCONJ ENGCABS ENGCARG ENGCFROMPOLAR ENGCTOPOLAR ENGCEXP ENGCLOG
ENGCSQRT ENGCPOW ENGCNEAR ENGCSHOW ENGSUM ENGMEAN ENGSTDDEV
ENGSTDDEVP ENGVAR ENGVARP ENGMEDIAN ENGQUANTILE ENGMINIMUM
ENGMAXIMUM ENGRANGEOF ENGSKEWNESS ENGKURTOSIS ENGWEIGHTEDMEAN
ENGGEOMEAN ENGCORREL ENGCOV ENGSPEARMAN ENGSTAT1 ENGLINEAR
ENGLOGMODEL ENGEXPMODEL ENGPOWERMODEL ENGFITOF ENGNORMPDF ENGNORMCDF
ENGNORMINV ENGTCDF ENGTINV ENGCHI2CDF ENGFCDF ENGBINOMPDF
ENGBINOMCDF ENGPOISSONPDF ENGPOISSONCDF ENGGEOMPDF ENGGEOMCDF
ENGHYPERPDF ENGEXPCDF ENGGCD ENGLCM ENGIQUOT ENGIREM ENGMODPOW
ENGMODINV ENGISPRIME ENGPRIMES ENGFACTORIZE ENGDIVISORS ENGTOTIENT
ENGFACT ENGCOMB ENGPERM ENGFIB ENGGAMMA ENGBETA ENGPOLYEVAL
ENGPOLYTRIM ENGPOLYDEGREE ENGPOLYADD ENGPOLYNEG ENGPOLYSUB
ENGPOLYSCALE ENGPOLYMUL ENGPOLYDIV ENGPOLYDERIV ENGPOLYINTEGRAL
ENGPOLYAREA ENGQUADROOTS ENGCUBICREAL ENGLINSPACE ENGGEOMSPACE
ENGCUMSUM ENGDIFF ENGMOVAVG ENGNORMALIZE ENGDOTOF ENGMAGOF ENGMETRE
ENGKILOMETRE ENGCENTIMETRE ENGMILLIMETRE ENGINCH ENGFOOT ENGYARD
ENGMILE ENGNAUTICALMILE ENGKILOGRAM ENGGRAM ENGTONNE ENGPOUND
ENGOUNCE ENGSTONE ENGSECOND ENGMINUTE ENGHOUR ENGDAY ENGKELVIN
ENGCELSIUS ENGFAHRENHEIT ENGPASCAL ENGKILOPASCAL ENGBAR ENGPSI
ENGATM ENGJOULE ENGKILOJOULE ENGCALORIE ENGKWH ENGBTU ENGWATT
ENGKILOWATT ENGHORSEPOWER ENGLITRE ENGMILLILITRE ENGCUBICMETRE
ENGGALLONUK ENGGALLONUS ENGMPS ENGKPH ENGMPH ENGKNOT ENGTOBASE
ENGFROMBASE ENGCONVERT ENGLIGHTSPEED ENGPLANCK ENGGRAVITATION
ENGSTDGRAVITY ENGELECTRONCHARGE ENGELECTRONMASS ENGPROTONMASS
ENGAVOGADRO ENGBOLTZMANN ENGGASCONSTANT ENGSTEFANBOLTZMANN
ENGFARADAY ENGVACUUMPERMITTIVITY ENGVACUUMPERMEABILITY ENGE ENGTAU
ENGROUNDTO ENGFRACOF ENGCLAMP ENGLERP ENGINVLERP ENGREMAP
ENGSMOOTHSTEP ENGSIGN ENGNEAR ENGSIGFIG ENGSCI ENGCOMMAS ENGDERIV
ENGDERIV2 ENGINTEGRATE ENGSUMOF ENGPRODOF ENGZEROOF ENGMINOF
ENGMAXOF
-- seed-fin --
FINPCT FINASPCT FINEFF FINNOM FINRULE72 FINREAL FINPCTCHG FINTOBPS
FINFROMBPS FINPV FINFV FINPVANN FINFVANN FINPVANNDUE FINFVANNDUE
FINPMT FINPMTDUE FINNPER FINSIMPLE FINCOMPOUND FINSUM FINMEAN
FINSTDDEV FINSTDDEVP FINVAR FINVARP FINMEDIAN FINQUANTILE FINMINIMUM
FINMAXIMUM FINRANGEOF FINSKEWNESS FINKURTOSIS FINWEIGHTEDMEAN
FINGEOMEAN FINCORREL FINCOV FINSPEARMAN FINNORMP FINTDIST FINSTAT1
FINLINEAR FINLOGMODEL FINEXPMODEL FINPOWERMODEL FINFITOF FINGOALPMT
FINRETIRE FINNETWORTH FINBUDGETOF FINDCAOF FINBALANCE FINAMORTAT
FINSCHEDULE FINEXTRAPAYOFF FINCARDPAYOFF FINCARDMINPAYOFF FINNPV
FINNPVOF FINIRR FINNFV FINMIRR FINPAYBACK FINPAYBACKDISC FINCAGR
FINROI FINRSTAT FINYIELD FINDIVGROWTH FINMARGINOF FINMARKUPOF
FINSELLFROMMARGIN FINCOSTFROMMARGIN FINBREAKEVENOF FINPAYROLLOF
FINSL FINSLBOOK FINSYD FINDB FINFACT FINCOMB FINPERM FINACTUAL
FIN360 FIN365 FINANNUAL FINSEMI FINQUARTERLY FINDATE FINDATESHOW
FINDATEOF FINISLEAP FINDAYSINMONTH FINSERIAL FINFROMSERIAL
FINDATEADD FINDATEADDMONTHS FINDAYS360 FINDATEDIFF FINYEARFRAC
FINCOUPONSBEFORE FINLASTCOUPON FINACCRUED FINBONDPRICE FINYTM
FINMDUR
-- seed-alg --
ALGSIMPLIFY ALGEXPAND ALGFACTOR ALGSOLVE ALGDESOLVE
-- seed-neural --
NEURALNEW NEURALNEWSCALED NEURALPREDICT NEURALTRAIN
NEURALREGRESSPLAN NEURALCLASSPLAN NEURALFIT NEURALPROBS
NEURALCLASSOF NEURALSOFTMAX NEURALARGMAX NEURALONEHOT NEURALMSE
NEURALACCURACY NEURALLOGLOSS NEURALCLASSACCURACY NEURALSCALERFIT
NEURALSCALERAPPLY NEURALSAVE NEURALLOAD NEURALSAVEBIN NEURALLOADBIN
NEURALMODEL NEURALMODELSAVE NEURALMODELLOAD NEURALMODELPREDICT
-- seed-recio --
RECOPEN RECCOUNT RECFIELDS RECGET RECALL RECPUT RECAPPEND
-- seed-isam --
ISAMOPEN ISAMCOUNT ISAMFIELDS ISAMHAS ISAMGETOR ISAMALL ISAMRANGE
ISAMINSERT ISAMUPDATE ISAMDELETE
-- seed-scribbler --
SCRIBOPEN SCRIBWIDTH SCRIBHEIGHT SCRIBTITLE SCRIBPIXEL SCRIBFILL
SCRIBTEXT SCRIBGETPIXEL SCRIBBLIT SCRIBSAVE
-- seed-net --
NETGET NETREQUEST
-- seed-turtle --
TURTLEOPEN TURTLEFORWARD TURTLEBACK TURTLERIGHT TURTLELEFT
TURTLESETHEADING TURTLEHOME TURTLEPENUP TURTLEPENDOWN TURTLEGOTO
TURTLESETPEN TURTLEWHERE TURTLEBLIT TURTLESAVE
-- seed-plotter --
PLOTOPEN PLOTTITLE PLOTBLIT PLOTSAVE PLOTHISTOGRAM PLOTSCATTER
PLOTBOX PLOTBAR PLOTPIE
-- seed-https --
HTTPSTATUS HTTPREASON HTTPBODY HTTPHEADERS HTTPHEADER HTTPSGET
HTTPSREQUEST
-- seed-regex --
RXTEST RXFIND RXFINDALL RXGROUPS RXREPLACE RXSPLIT RXQUOTE RXWHY
-- seed-sparse --
SPARSE SPDENSE SPENTS SPNNZ SPGET SPCOLIX SPCOLVS SPCOLDOT SPMATVEC
SPVECMAT SPTRANSP SPADDROW SPADDCOL
-- seed-mip --
MIPPROBLEM MIPSOLVE MIPFIXED MPSMIP MPSMIPLOAD
-- seed-terminal --
PRINT
-- seed-geo --
GEODIST GEOBEARING GEODEST GEOMID GEOROUTELEN GEOCROSSTRACK
GEOBOUNDS GEOAREA GEOSIMPLIFY GEODMS GEOPARSE GEOHASH GEOLOCATOR
GEOCOMPASS GEOJULIAN GEOSUNRISE GEOSUNSET GEODAYLENGTH GEOHORIZON
-- seed-julian --
JULDATE JULDATESHOW JULDATEOF JULISLEAP JULDAYSINMONTH JULSERIAL
JULFROMSERIAL JULDAYNUMBER JULADDDAYS JULADDMONTHS JULDIFF
JULWEEKDAY
-- seed-ephemeris --
EPHSIDEREAL EPHSUN EPHMOON EPHMOONDIST EPHMOONPHASE EPHMOONAGE
EPHMOONRISE EPHMOONSET EPHMERCURY EPHVENUS EPHMARS EPHJUPITER
EPHSATURN EPHPLANET EPHALTAZ EPHSEPARATION
-- seed-sinq --
SORTBY SORTBYDESC GROUPBY MINBY MAXBY FINDFIRST TAKEWHILE SKIPWHILE
DISTINCT UNION INTERSECT EXCEPT CHUNK
-- shell --
SAVE LOAD TAPESAVE RESET
| Tool | What it does |
|---|---|
eval | Evaluate lines in order; answers what each printed, said, and left on the stack. A drawing blitted during the line comes back as a picture in the same answer. What a turn prints is carried to 200 lines or 16,000 characters, and the answer says how many lines were left out. |
define | A whole : NAME … ; at once. |
put | Text into a register, whole, past the tokenizer: the way in for a JSON document or anything a line cannot carry. "doc" RCL reads it back. Text already in a file under the root needs no put: "data.json" READFILE reads it in a line. |
stack | The stack as it stands. |
help | One word's exact stack effect and description, read from the running dictionary. |
view | The body of a word the session defined. |
words | Every word, grouped by seed, or by what touches the world. |
canvas | The current drawing as a PNG, blitted or not. |
save / load | Write the session's definitions to a file under the root, and read them back. The descriptions name the root, and a path outside it is refused. |
tape | Every line entered and what it answered, one row each, counted from 1. With from and to, only those rows and a line saying how many the tape holds; with a file, written out under the root. |
machines / subject | What can be taught, and a teaching card for one of them: the page's prose with the live word list. |
reset | A fresh dictionary: empty stack, no tape, only the words it started with. |
abandon | A new engine, for a session stuck in a fetch that will never return. |
Resources carry the grounding: sparky://grounding/shape,
…/reckoner, …/surface,
sparky://dictionary, and sparky://machines/{name} for
a subject card. Two prompts come with it: teach for a lesson in
one machine's subject, and check-my-working for a student who has
an answer and wants it recomputed rather than agreed with.
The seed fold is the only thing that decides which words exist. That is why
Sparky is a mill of its own rather than a narrower grant over halifax's. A
capability grant does not gate a dictionary: withholding
scribbler from halifax would leave SCRIBOPEN
registered, listed by WORDS, answered by HELP, and
returning nonsense to whoever called it.
| Left out | Why |
|---|---|
seedbuzzer | Sound. The server does not control the host's audio lifetime, cannot know whether anything is listening, and has no use for it. |
seedkeys | Pure, and pointless. It classifies keystrokes, and there is no keystroke source. |
seedvt100 | Pure, and pointless. It builds escape sequences for a screen that does not exist; a client receiving them in JSON is worse off than one told the word is absent. |
Ask HELP BUZZPLAY and you are told it is not a word in this
dictionary. That is the accurate answer, and the one a model can act on.
INPUT, INPUTLINE and INKEY needed no
excluding: no seed registers them. Neither does any listening, accepting or
blocking-wait word. seednet bridges the request half and nothing
else, so the network surface is outbound by construction rather than by
policy.
Files live under one root, and the root is a boundary rather than a base.
A path climbing out with .., an absolute path elsewhere, or a
symbolic link along the way (a file that is a pointer to somewhere
else) touches nothing outside it. A read of such a path is refused. A
write, a delete or FILEEXISTS answers False. See
Capabilities and grants.
| Command | What it does |
|---|---|
./build.ps1 run | The dictionary at a terminal: the debugging affordance, not the product. End input (Ctrl-Z, or Ctrl-D) to leave. There is no QUIT: a server has no session to leave, and the dictionary a person types at must be the one a client is handed. |
./build.ps1 test | The headless suite: no terminal, no display, no network, and no files at all. |
./build.ps1 build | Weave the mill into bin/. |
hosts/mcp/build.ps1 | The server: restore, build, test. |
A sparkyrc in the root loads on the way in, exactly as
halifaxrc does. Absent is silence. Present, it is loaded and the
word count reported. One that will not load says so, and the session starts
anyway. abandon reads it again, because that is a new session.
reset does not, because RESET means a fresh
dictionary and nothing else.
| File | What's in it |
|---|---|
mills/sparky/sparky-core.shoddy | The fold (thirty-eight seeds in halifax's own order) and the four words the host owns. Pure: nothing here reads, writes or prints. |
mills/sparky/sparky.shoddy | The console shell: a prompt, and sparkyrc. Everything that touches the world. |
mills/sparky/test.shoddy | The headless suite. |
hosts/mcp/Shoddy.Mcp | The session, the headless canvas, the grounding index, the tools, and the protocol loop. |
hosts/mcp/sparky | The executable: arguments, the root, and the stdio streams. |
It does not include halifax-core.shoddy, and that is the
decision most likely to be undone by someone tidying. Halifax's layer is a
terminal REPL, a read-evaluate-print loop, the machinery of a prompt: a
banner, QUIT, the interception that lets SAVE mean
something typed as a whole line. Sparky's caller is a model calling tools.
Its door is the engine's own: RckEval, which
reckoner calls the only door a
shell needs. Borrowing the REPL would import a prompt model there is no prompt
for.
The protocol is hand-rolled over System.Text.Json. A protocol
SDK (a ready-made library for speaking MCP) exists only in preview
versions. It would become this repository's most volatile dependency, and it
would sit on its least stable layer. The cost of not taking it is a few
hundred lines.
The mill's own suite grades the fold in both directions. Every seed folded in is present and reachable. Every word of the three seeds left out is absent, which is the half that needs a test. A seed that quietly rejoined would leave a client holding words the server cannot supply, and nothing else in the tree would notice.
It also runs a fuzz list, a stock of deliberately broken input: every malformed line, every host word misused, and the algebra shapes nothing had ever fed it. Each is followed by a plain sum, so a refusal that lost the state shows up as the next line failing too.
| Machine | Why | |
|---|---|---|
| cuttle | CutStr is the cell the put tool banks. | |
| dict | DictPut on the registers is how put stores it, the same call STO makes. | |
| reckoner | The whole calculator: the stack, the dictionary, the registers, UNDO, the tape, HELP and WORDS. RckEval is the door every tool goes through. | |
| seq | Filter and Length count the words a session has defined, for the load report. | |
| str | Split and Trim turn sparkyrc into lines, taking the carriage return off a file written on another platform. |
And, through the fold, every machine the thirty-eight seeds bridge, which is most of the library.