Program with Spark
Canonical programming guide for the Spark language on SoapBox. Statement reference: Language reference.
Primary today: CLI ./spark (this guide). Verified IDE language ops:
ide new|open|save|run|buffer|ask|show and ide keys / ide key —
IDE status. Editor paint is a wire + PPM (gutter/glyphs + AI
strip; make test-ide-paint), not a .spark statement. ide show
reuses real spark-engine-show. See verified ops below.
Rule: ops and flags below match asm/ dispatch, shipped
examples/*.spark, and commands verified under --dry-run (or companion
--dry) on this tree.
| Doc | Role |
|---|---|
| This file | How to build, run, and write .spark (CLI) |
| IDE status | Verified ide ops; interim Cursor optional |
| Language reference | Full statement reference |
ASK_LIVE.md (ASK_LIVE.md in repo) |
Live Bifrost ask |
VOICE.md (VOICE.md in repo) |
STT/TTS / PSTN |
ENCRYPT_GATEWAY.md (ENCRYPT_GATEWAY.md in repo) |
Encrypt-to-model |
MODEL_ANALYSIS.md (MODEL_ANALYSIS.md in repo) |
Model analyze/improve |
OS_DESIGN.md (OS_DESIGN.md in repo) |
OS blueprints |
| Self-host path | A+B+C self-host path (GAS scaffold → B/C → Spark) |
Repos: language SoT = /home/mike/workspaces/spark. Browser product
host = /home/mike/workspaces/spark-browser (Qt shim). Engine-B asm
examples exist in-repo (see §10); they are not a substitute for the
language CLI workflow.
1. What Spark is
You write .spark files. Today they run on the shipped ./spark
ELF GAS scaffold (asm/). Authoring SoT DECIDED (A+B+C):
self-host destination (A), thin C bootstrap (bootstrap/), Spark-native
assembler (sparkasm/). Do not treat GAS as forever SoT; do not start
a Rust/Python VM from IDE work. See Self-host path.
Docs cover proven ops and fixture HTML examples you can dry-run today.
| Tier (today) | What | Path |
|---|---|---|
| Machine code | CPU runs ELF | ./spark (GAS scaffold) |
| Assembly scaffold | GAS → as → ld |
asm/*.s (disposable) |
| B bootstrap | Thin C dry VM | bootstrap/ → spark-bootstrap |
| C assembler | .sasm → ELF .o |
sparkasm/ (phase-1) |
| HDL (optional stub) | classify sketch | hdl/classify_score.v |
Live HTTP for ask is a companion (./spark-ask-http) forked only
under --live. Dry-run does not dial the network for ask.
This is not Apache Spark / Databricks.
2. Build
cd /home/mike/workspaces/spark
make # ./spark + companions
./spark --version # e.g. spark 0.6.0 (x86_64 asm + companions)
make machine-proof # file(1) + objdump of _start
| Target | Role |
|---|---|
make / make all |
VM + companions |
make test |
tests/run_dry.sh + HDL check |
make test-examples |
every examples/*.spark under --dry-run |
make test-e2e-browser |
browser dry E2E (no display) |
make ide |
interim Cursor workspace open (not product IDE) |
make clean |
remove ELF + companion binaries |
If make fails on asm/engine_*.s while parallel engine work is in
flight, that is an engine-lane assemble/link issue — not a reason to
invent a different language runtime. Re-run make when those objects
link; language examples below were verified when ./spark was present.
3. Run: dry vs live
Verified usage from bare ./spark:
Usage: spark --dry-run [--allow-net] [--allow-net-capture] <file.spark>
spark --live <file.spark> # ask → AI_GATEWAY_URL
spark --live --pstn-live <file> # PSTN off unless SPARK_PSTN=1
spark --version
There is no ./spark ide ELF subcommand. IDE core is language
ops (ide open|save|run|…) — IDE status,
examples/ide_hello.spark.
./spark --dry-run examples/hello.spark
./spark --dry-run examples/ide_hello.spark
export AI_GATEWAY_URL=http://127.0.0.1:4000
export OPENAI_API_KEY=… # sk-bf-* ; never commit
./spark --live examples/ask_live.spark
| Flag / gate | Effect |
|---|---|
--dry-run |
Offline / fixture path (CI) |
--live |
Live companions (ask, speech, GUI, …) |
--allow-net |
review url may fetch remote http(s) |
--allow-net-capture |
Live AF_PACKET capture (needs CAP_NET_RAW) |
--pstn-live + SPARK_PSTN=1 |
PSTN dial (off by default) |
4. First program
File: examples/hello.spark (verified rc=0 under --dry-run):
model code
ask "Explain gravity in one sentence" -> text
print text
./spark --dry-run examples/hello.spark
Lexical rules (from LANGUAGE.md / working examples):
- Comments:
#to end of line - Strings:
"..." -> namebinds a result|may prefix a pipeline step
Optional spark.toml documents defaults; the dry VM does not parse
it for aliases — use a model statement in the .spark file.
5. Syntax (verified examples)
Each subsection names the file that was dry-run successfully.
5.1 model + ask
examples/hello.spark, examples/ask_live.spark
model code # or fast | best (gateway aliases)
ask "Explain gravity in one sentence" -> text
print text
Live ask needs --live + ./spark-ask-http + env — ASK_LIVE.md (ASK_LIVE.md in repo).
5.2 ask probe / gateway probe
examples/ask_probe.spark, examples/gateway_probe.spark
(requires companion ./spark-ask-probe; dry prints Infisical readiness
JSON, no public HTTP)
ask probe -> info
gateway probe -> info
./spark --dry-run examples/ask_probe.spark
./spark-ask-probe --dry
Live public probe / HTTP 401 → credential unavailable (exit non-zero); see ASK_LIVE.md.
5.3 classify
examples/classify_intent.spark
classify Intent { support, sales, spam }
from "My washer is broken and I need help"
min_confidence 0.7
-> intent
classify multi Tags { support, sales, spam }
from "Please help me buy a card, what is the price?"
-> tags
5.4 extract
examples/extract_person.spark
extract Person {
name: string
age: int
} from "Ada Lovelace was born in 1815" -> person
5.5 pipeline
examples/pipeline_translate.spark
let doc "Laundry machines need regular cleaning and balance checks."
pipeline {
ask "Summarize: {doc}" -> summary
| ask "Translate to Spanish: {summary}" -> es
}
5.6 tool / with tools
examples/tool_agent.spark
tool weather(city: string) -> string {
"stub:local"
}
with tools [weather] {
ask "What's the weather hint for Des Moines?" -> answer
}
Fail-loud cases live under examples/neg/ (e.g. with_no_tool.spark).
5.7 review / builder / implement
examples/review_builder.spark, examples/ide.spark
(dry-run writes out/program.spark; not the product IDE)
review path "examples/fixtures/sample.js" -> report
review url "file://examples/fixtures/sample.js" -> report_url
review text "function x(){ eval(y); }" -> report_text
builder prefer lower request "add classify Intent and wire voice turn" -> patch
implement patch into "out/program.spark"
Remote http(s) review needs --allow-net. Never evaluates web JS.
5.7b IDE core (ide)
Verified (examples/ide_hello.spark, examples/ide_ask.spark,
examples/ide_show.spark, make test, ask ship dc54d92, this tree
rc=0):
ide new
ide open "examples/hello.spark" -> opened
ide buffer -> shown
ide save "out/ide/ide_hello_saved.spark" -> saved
ide run -> ran
ide ask -> reply
ide show -> shown
| Op | Behavior |
|---|---|
ide new |
Clear buffer (+ optional "path") |
ide open "path" |
Load file into buffer |
ide save / ide save "path" |
Write buffer to path |
ide buffer |
Terminal dump of buffer |
ide run |
Flush; fork/exec ./spark (--dry-run or --live) |
ide ask ["instruction"] |
Buffer (+ optional quote) → real ask_run_prompt; dry fixture (Gravity); live ./spark-ask-http; AI strip via ide_ai_set + editor.ppm |
ide show ["path.ppm"] |
After paint: real engine_window_show; dry validates PPM + show.json; live forks ./spark-engine-show |
./spark --dry-run examples/ide_ask.spark
# dry → fixture reply (e.g. Gravity); live → spark-ask-http
./spark --dry-run examples/ide_show.spark
# dry → validate editor.ppm; live → spark-engine-show
./spark --dry-run examples/ide_run_show.spark
# open → run (child) → show (engine validate + show.json)
./spark --dry-run examples/ide_ask_show.spark
# open → ask (AI strip) → show (engine validate + show.json)
./spark --dry-run examples/ide_save_reopen.spark
# open → save → reopen; saved file sha256/size == hello.spark
Display window = engine companion. Details: IDE status. Reports:
reports/spark-ide-ask-20260831.md,
reports/spark-ide-show-20260831.md,
reports/spark-ide-run-show-20260831.md,
reports/spark-ide-ask-show-20260831.md,
reports/spark-ide-save-reopen-20260831.md.
5.7c IDE keymap (ide keys / ide key)
Verified (examples/ide_keys.spark, commit 51fbe8f + show polish,
save token proof examples/ide_keys_save.spark, run token proof
examples/ide_keys_run.spark, this tree rc=0).
Asm: asm/ide_keys.s. Script fixtures:
examples/fixtures/ide/cmds.txt, cmds_save.txt (s → save),
cmds_run.txt (r → run).
ide keys "examples/fixtures/ide/cmds.txt"
ide key open "examples/hello.spark"
ide key show
ide key save
ide key run
ide key quit
| Op | Behavior |
|---|---|
ide keys "script" |
Read command file; run quit/save/run/open/show |
ide key quit\|save\|run\|open\|show ["path"] |
Single keymap command |
| Script tokens (proven) | q/quit, s/save, r/run, o path/open path, w/show |
./spark --dry-run examples/ide_keys.spark
# prints {"op":"ide.keys",…}; appends out/ide/keys_trace.jsonl
./spark --dry-run examples/ide_keys_save.spark
# script `s` → {"cmd":"save","path":"examples/hello.spark"}; dry no write
./spark --dry-run examples/ide_keys_run.spark
# script `r` → {"cmd":"run","path":"examples/hello.spark"}; dry no fork
Dry-run: traces only for save/run (no write / no fork). Open still
does real open+read and paints editor.ppm. Show dry-validates
via engine_window_show. No mouse GUI. Do not invent key tokens.
5.7d Editor paint (PPM — not a language op)
Verified (make test-ide-paint, status strip b029a4a + dirty *
after ide new + AI panel on dc54d92, PASS this tree). There is
no ide paint statement in .spark. Paint is asm ABI: core
exports ide_buf / ide_buf_len / ide_dirty and calls
ide_paint_bind after new/open/save; ide ask calls
ide_ai_set then rewrites PPM. Paint owns ide_cursor /
ide_status_set / ide_ai_set and writes out/ide/editor.ppm (top
status path[+*] + gutter + glyphs + cursor + bottom AI strip).
ide new → dirty *; open/save clear. Keymap n→new not
proven. Example: examples/ide_dirty_status.spark.
make test-ide-paint
# → {"op":"ide.paint","ppm":"out/ide/editor.ppm",…}
# → PASS ide-paint-ppm … / ide_status_strip_ppm
./spark --dry-run examples/ide_dirty_status.spark
# → dirty true on new; status_dirty.txt ends *; save clears
Report: reports/spark-ide-status-strip-20260831.md,
reports/spark-ide-dirty-status-20260831.md.
5.8 Voice
examples/voice_turn.spark (and VOICE.md examples)
voice {
listen -> user
classify Intent { support, sales } from user -> intent
ask "Reply helpfully to: {user}" -> reply
speak reply -> "out.wav"
}
Also present: voice_reviewer.spark, voice_coder.spark,
voice_copy.spark, voice_model.spark, voice_pstn.spark,
voice_live.spark. PSTN stays gated off unless --pstn-live +
SPARK_PSTN=1.
5.9 Browser / MITM (language driver)
Canonical dry entry: examples/browser_main.spark
browser run "examples/browser_main.spark" -> session
browser goto "https://example.com/" -> page
mitm enable -> mitm_session
mitm filter "example\\.com" -> filt
mitm har export -> har
| Mode | Command (verified entrypoints) |
|---|---|
| Dry | ./spark --dry-run examples/browser_main.spark |
| Live product | cd ../spark-browser && make run → ./spark --live browser/run.spark |
| Dry E2E | make test-e2e-browser |
Also dry examples: browser_ca.spark, browser_h2.spark,
browser_quic.spark, browser_cdp.spark, browser_mitm.spark,
browser_show.spark (PPM show; dry display:false; live X11 via
./spark-engine-show — see §10 Live X11 show).
browser gui requires --live (forks ./spark-browser-host). Dry
browser gui fails by design (examples/neg/browser_gui_dry.spark).
Do not document python3 -m spark_browser run as the product entry.
5.10 Encrypt gateway
examples/encrypt_gateway.spark (needs ./spark-enc-gateway)
crypto keygen -> key
encrypt gateway enable key
gateway encrypt on
encrypt seal text "…" -> blob
encrypt open blob -> plain
ask "…" -> reply
gateway encrypt off
5.11 Network / binary / CUDA / OS / model
| Area | Example file | Notes |
|---|---|---|
| Network analyze | network_analyze.spark |
open fixture pcap |
| Capture probe | network_capture_probe.spark |
claimed:false |
| Capture | network_capture.spark |
fixture unless --allow-net-capture |
| Binary | binary_any.spark |
out/decompile/… |
| CUDA / memory | cuda_mem.spark |
/dev/nvidia* |
| PCIe | cuda_pcie.spark |
sysfs link |
| OS blueprint | os_agentos.spark |
out/os/ stubs; never reboot |
| Model improve | model_improve.spark |
fixtures; build ≠ train@* |
6. Layout
examples/*.spark # programs
docs/ # this guide + LANGUAGE.md + …
asm/*.s # VM (do not thrash engine_* from app docs)
tools/ # companions (ask-http, review-url, …)
templates/ # os / browser scaffolds
out/ # implement / HAR / decompile / engine artifacts
tests/ # dry harness
spark.toml # documented defaults (not VM-parsed for aliases)
spark.code-workspace # Cursor workspace (IDE.md)
7. Debugging
| Symptom | What to check |
|---|---|
| Usage printed | Need --dry-run or --live plus a .spark path |
| Missing companion | make / make companions; then re-run |
ask probe fail |
Infisical / credential path — see ASK_LIVE.md |
review url blocked |
Remote needs --allow-net; use file:// offline |
browser gui in dry |
Expected fail — use dry browser run or --live |
| Prefer GPU 2 | Refused (voice GPU) — see examples/neg/cuda_prefer_* |
Negative corpus: examples/neg/ (tests expect non-zero exit + error text).
Errors / exit codes (verified)
| Situation | Observed |
|---|---|
Bare ./spark (usage) |
exit 1 |
Success (examples/hello.spark dry) |
exit 0 |
Fail-loud language errors (unknown, with_no_tool, blocked review url, …) |
exit non-zero (typically 1) + error: / gate text |
./spark-ask-probe credential miss |
exit 4 (credential unavailable) |
Live net-capture without CAP_NET_RAW |
exit 4 (claimed:false) |
Do not assume every stderr line means a non-zero process exit without checking $? (pipes hide the VM’s code unless set -o pipefail).
8. Tests
make test
make test-examples
make test-e2e-browser
make test-ide-paint # PPM paint wire (not a .spark op)
make test stays offline for ask/vendor speech. If ./spark disappears
mid-suite while engine objects are relinking, re-run make then make test.
9. Hardware / asm
- Users program in Spark. The VM is asm→ELF.
- Do not edit
asm/engine_*.sfrom an app/docs lane while engine agents work that tree. make machine-proofshows ELF64 +_startdisassembly when the binary links.
10. Engine B (verified ops only)
Documented here only after dry-run (or gated fail) on this tree.
Phase-1 <script> → engine_js_eval. HTTPS uses OpenSSL BIO companion ./spark-engine-fetch-tls.
Dry pipeline (fetch→parse→css→layout→paint→show)
examples/engine_pipeline.spark (verified on this tree):
engine fetch "file://engine/fixtures/style_basic.html" -> body
engine parse "out/engine/body.bin" -> dom
engine css attach -> styles
engine layout -> boxes
engine paint boxes -> ppm
engine show "out/engine/pipeline.ppm" -> shown
./spark --dry-run examples/engine_pipeline.spark
# → out/engine/pipeline.ppm + out/browser/show.json
# {"op":"show","mode":"dry-run","display":false,…}
Table pipeline (real): examples/engine_pipeline_table.spark on
table_demo.html — cell borders + cell text (layout.table /
cell_text). Also examples/engine_fetch_parse_layout.spark.
Proofs: make test-engine-pipeline-table, make test-engine-layout,
make test-engine-fetch-parse-layout.
CSS padding (real): shorthand padding px on block/cell boxes
(e313b21); border-width px on table cells (60e2b44); display
subset includes table/table-row/table-cell, plus padding and border-width in px.
Artifacts: out/engine/body.bin, out/browser/engine/dom.json,
out/browser/engine/css.json, out/engine/pipeline.ppm,
out/browser/show.json. Layout without prior parse fail closed
(examples/neg/engine_layout_no_dom.spark).
Live X11 show (spark-engine-show)
Same .spark file; --live forks the existing companion (no Qt):
make spark-engine-show
./spark --live examples/engine_pipeline.spark
# companion: ./spark-engine-show --ppm out/engine/pipeline.ppm --hold 2000
# show.json → mode:live display:true path:out/engine/pipeline.ppm
Also: ./spark --live examples/browser_show.spark (fixture PPM) and
./spark --live examples/browser_engine_render.spark (render→same
pipeline.ppm). Needs DISPLAY. Longer view:
./spark-engine-show --ppm out/engine/pipeline.ppm --hold 5000
./spark-engine-show --dry --ppm out/engine/pipeline.ppm # no X11
Dry make test never forks X11. Report:
reports/spark-engine-show-live-20260831.md.
engine parse → out/browser/engine/dom.json
examples/engine_parse.spark (verified rc=0):
engine parse "engine/fixtures/hello.html" -> dom
Writes DOM JSON under out/browser/engine/dom.json
("op":"engine.parse", "engine":"spark-asm-html", node list).
engine fetch
| Input | Flag | Verified result |
|---|---|---|
file://… / local path |
none | OK → out/engine/body.bin (examples/engine_fetch.spark) |
http://127.0.0.1/… |
without --allow-net |
Fail — blocked by default (examples/neg/engine_fetch_blocked.spark) |
http://127.0.0.1/… |
with --allow-net |
OK — "transport":"asm-socket", "fetched":true (loopback fixture) |
https://… |
without --allow-net |
Fail closed (examples/neg/engine_fetch_https.spark) |
https://… |
with --allow-net |
OK — forks ./spark-engine-fetch-tls OpenSSL BIO (openssl-bio) |
engine fetch "file://examples/fixtures/engine/sample.html" -> body
# http/https with --allow-net:
# ./spark --dry-run --allow-net your_fetch.spark
HTTPS is not TLS-in-asm / not Python — companion only
(27252f4). PASS: engine_fetch_tls_companion_* +
engine_fetch_https_openssl_bio.
js phase-1 only
Verified: examples/js_phase1.spark, browser/engine/js.spark.
js eval "1+1" -> sum
js eval "'a'+'b'" -> cat
js eval "-1" -> neg
js eval "var x = 1; x+2" -> vsum
js run "console.log(1+1)" -> log
js console -> buf # dump console buffer (browser/engine/js.spark)
js selftest -> ok
Runtime prints phase1 numbers/strings/+/unary- /var-num/console.log.
Artifacts: out/engine/js_result.txt, out/engine/js_console.txt.
DOM hook (real): engine parse walks <script> text children and
calls engine_js_eval (same phase-1 slice). Proof:
engine/fixtures/hello.html (1+1), engine/fixtures/script_log.html,
./engine/tests/js_script_dom.sh.
Unary -: prefix on a number primary (-1, -1+3, --1).
Binary - and -'str' fail loud.
var number assign: var x = 1 / var x = 1; x+2 (RHS must be
number). var x = 'a', undeclared x, let/const fail loud.
Phase-1 slice: numbers, strings, +, unary -, var num, and console.log.
Engine B pixels are the product render path. See Self-host path.
11. What Spark includes today
Real surface area you can dry-run now:
model / ask / classify / extract / pipeline /
tool / review→builder→implement / voice / gateway encrypt /
verified ide ops / engine B pipeline. A+B+C:
Self-host path, bootstrap/README.md,
sparkasm/README.md, selfhost/README.md.
12. Cheat sheet
make && ./spark --version
./spark --dry-run examples/hello.spark
./spark --dry-run examples/ide_hello.spark
./spark --dry-run examples/ide_ask_show.spark
./spark --dry-run examples/ide_save_reopen.spark
./spark --dry-run examples/engine_pipeline.spark
./spark --dry-run examples/engine_pipeline_table.spark
./spark --dry-run examples/js_phase1.spark
make test-bootstrap
make -C sparkasm test
make test-selfhost-lex
# make test-ide-paint # PPM + status strip (not a .spark op)
# optional interim editor: make ide (Cursor host — see IDE.md)
Next: IDE status (verified ide ops; CLI stays primary).