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-four of the reckoner's thirty-seven 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 wearing 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 isn't about any project.
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 earn 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 earns 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. Numbers and strings go on the stack; words work on what
is there. `3 4 +` leaves 7. The stack is shown after every line, top
value labelled `x`, then `y`, `z`, `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**, and prints 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 KEYABLE ABORTS.** There is no line you can type that ends
the session. If something is wrong you get a refusal and another
chance, so trying a thing is cheap — 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 facts that surprise everything that has seen a Forth
- **A USER WORD TAKES EXACTLY ONE CELL.** Not "as many as it pops" — one.
If a word needs a second argument, bank it in a register first with
`STO` and read it inside with `RCL`. Writing a two-argument definition
is the single most common mistake here and it will be refused.
- **RECURSION IS IMPOSSIBLE, ON PURPOSE.** A definition is validated
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, and 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, and reading the
effect line is the whole cure.
## 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 a word exists — every word carries its own effect and
description, so there is never a reason to invent one.
## When a line is refused, the refusal tells you the fix
This is why trying is cheap 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 a width, a height and a name
{ 1 2 2 3 } PLOTHISTOGRAM ?: PLOTHISTOGRAM needs 3, the stack holds 2
"3" 4 + ?: + is not defined for STRING and NUMBER
Every one of those 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 narrate it as a failure — it is how you find the right
line, and it costs nothing.
## Where the calculator CANNOT catch you
Everything above is caught. These are not: the line is accepted, an
answer appears, and it is the wrong answer. There are four of them, and
they are the reason to read this section rather than skim it.
**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` puts it 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
Same for `VAR` and `VARP`. Both are right answers to different
questions; 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; `STD` shows them again. Never read a
`FIX`ed display back as the value.
**Numbers are IEEE doubles, so money is its own kind.**
0.1 0.2 + x: 0.3
0.1 0.2 + 0.3 = x: False
The display rounds; the comparison does not. For currency use `MONEY`
and its words, which are exact and which 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 needing two arguments, banking one first:
0.2 "rate" STO
: WITHVAT DUP "rate" RCL * + ;
250 WITHVAT
x: 300
A matrix, built rows-and-columns first:
2 2 { 1 2 3 4 } MAT LINDET
x: -2
A chart, which arrives as a picture with the third line:
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 hands you the Halifax dictionary — the whole of the reckoner's
standard library at an RPN prompt — as callable tools, plus the
grounding to use it correctly. 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.
**Compute the answer. Do not estimate it.** That is the whole point 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
Seven 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, 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
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
shell SAVE, LOAD, TAPESAVE and RESET, called as tools
Ask `subject` for any of those and you 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, `CLOSEALL` shuts them all. This is why `UNDO` cannot leave a
resource stranded: 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; 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.
## Four things that catch a text-only caller harder than a person
- **`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 at all, 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 — though this server keeps the last captured frame, 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` and `RESET` 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.
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 climb out of it — `"../elsewhere"`, or an absolute path
somewhere else — is refused before anything is touched, so use plain
names.
Thirty-four 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
-- 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. |
define | A whole : NAME … ; at once. |
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. |
tape | Every line entered and what it answered; with a file, written out. |
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 honest 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 really a signpost to somewhere
else — are all refused before anything is touched. 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-four 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 honestly 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 | |
|---|---|---|
| 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-four seeds bridge — which is most of the library.