Frothy persists the overlay image and rebuilds the base image at boot.
Image Shape
base image and overlay image (image model)
Layer: core
Behavior: The base image contains built-ins, foreign bindings, standard
library, and board library. The overlay contains user-created top-level state
after boot.
Example:
`gpio.write` is a base-image slot; `myProgram` is usually an overlay slot.
Worked example:
pulse is fn [ led.on: ]
message is "draft"
Here pulse and message live in the overlay. led.on remains a base-image
name even when your overlay code calls it.
rebinding and base-name shadowing (image model)
Layer: core
Behavior: Rebinding updates the current value stored in a stable slot. Base
names may be shadowed by overlay writes, but dangerous.wipe restores the
boot-rebuilt base value.
Example:
blink is fn [ 99 ]
dangerous.wipe
Worked example:
see blink
blink is fn [ "temporary overlay version" ]
see blink
dangerous.wipe
see blink
The first and third see blink come from the base image. The middle one is
your overlay shadowing that same stable slot name.
Persistence Operations
save (interactive base image)
Layer: core
Behavior: Snapshots the overlay image only. The saved walk includes overlay
top-level bindings plus persistable objects they own.
Example:
save
Worked example:
record Cursor [ x, y ]
cursor is Cursor: 2, 3
save
What is saved here is the overlay slot cursor plus the persistable record
value it owns.
At the prompt, save has three non-error response shapes:
okwith no notice means the new overlay was made durablenotice: saved; handle values stored as nil (100)followed by its details andokmeans the overlay was made durable, with the named slots written asnilnotice: not saved (13)followed by its details andokmeans evaluation completed, but the durable write did not happen
save can also fail outright—for example because the snapshot exceeds
capacity or storage I/O fails. Those responses use an error: headline and do
not end in ok.
Live Handles and save
A handle is a live connection to hardware. Nothing in a saved image can carry
one: after a reboot the peripheral is closed and the number that named it means
nothing. So save stores those slots as nil and says which ones:
> save
notice: saved; handle values stored as nil (100)
detail: 'led' was stored as nil - recreate it in boot so a reboot brings it back
ok
>
Your running program is untouched—led still holds its open channel, the LED
stays lit, and you keep working. Only the image says nil there. Put the
opening step in boot and the next reboot brings the resource back:
led is pwm.open: $led_builtin, 1000
boot is fn [
set led to pwm.open: $led_builtin, 1000
]
save
A few cases still refuse rather than write nil, because nil there could
lose something the device cannot recover: a live Bluetooth connection, a slot
installed in library mode, and more handle-bound slots than the device can hold
at once. Those keep the older response:
> save
notice: not saved (13)
detail: cannot save slot 'link' - bound to a live handle or a Bytes value
ok
>
The live overlay remains usable and the previously saved overlay remains
intact. Close the resource, rebind the named top-level slot to nil or another
persistable value, and save again.
Both notice shapes belong to the complete prompt form—bare save or save:.
Inside another word or expression, save: does not save at all: it answers
error: prompt only (26) and stops the rest of that form. Saving replaces the
image the running program is executing from, so it belongs to the prompt; the
same holds for restore and dangerous.wipe. The error is catchable with
attempt/rescue. See the complete
error and notice contract
.
Buffers and save
save keeps the contents of a buffer
, and the
board has them again after a restart. A buffer from library mode goes back to
its contents in the library image at each save, restore and wipe-user. A
buffer that both the library and your program hold does the same, as does a
library Cells value.
A buffer keeps its capacity in the Text pool also when it is empty. When no
name holds a buffer, save, clear, restore or a restart frees it. A
running program cannot make, resize or free a buffer; only a top-level
definition makes one.
Flash keeps the older saved image until a later save writes over it. So saved
contents can stay in flash after you change them. dangerous.wipe removes
them.
restore (interactive base image)
Layer: core
Behavior: Replaces the live overlay with the persisted overlay. If restore
fails, the runtime must remain in a usable base state.
Example:
restore
Worked example:
record Cursor [ x, y ]
cursor is Cursor: 2, 3
save
set cursor->x to 9
restore
cursor->x
After restore, cursor->x is back to 2.
dangerous.wipe (interactive base image)
Layer: core
Behavior: Clears both the live overlay and the stored overlay and returns the
running image to base-only state.
Example:
dangerous.wipe
Worked example:
record Session [ count ]
session is Session: 4
save
dangerous.wipe
session
After dangerous.wipe, reading session is an error because both the live and
saved overlay copies are gone.
Boot and Recovery
boot (top-level slot)
Layer: core
Behavior: If boot holds Code after restore, the runtime executes it before
entering the prompt.
Example:
boot is fn [ led.on: ]
Worked example:
record State [ ready ]
state is State: false
boot is fn [
set state->ready to true;
led.on:
]
save
On the next restore path, boot runs before the prompt and can finish that
small startup step.
safe boot (recovery surface)
Layer: core
Behavior: On an ESP32 board, Ctrl-C or the BOOT button in the 600 ms after
start skips restore and boot, so a bad saved program cannot keep the prompt
closed. An RP2040 board has no such window; Ctrl-C stops a boot that runs.
Example:
Press Ctrl-C in the safe-boot window. Then run `restore` and `see boot`, or run `dangerous.wipe`.
Typical recovery flow:
1. Connect to the board, and reset it.
2. Press Ctrl-C in the safe-boot window. On RP2040, press it while `boot` runs.
3. Run `restore`, and then `see boot`.
4. Define `boot` again and `save`, or run `dangerous.wipe`.
In safe boot, the board does not load the saved program. So a save writes
only the words that you define after the start. Run restore before save to
keep the saved program.
An Image From Another Release
A saved image records the image format and the profile hash of the firmware
that wrote it; status shows the hash as profile_hash. A device reads a
saved image only when both match its own. When either differs, it reports
other release at boot and from restore.
The profile hash covers the limits and features of the profile, the slot, kind, value, arity and signature of each base word, the source of the base library, and the names and arities of the library natives built into the firmware. It does not cover the code inside a native or the names of base words. A release that keeps the hash and the image format reads the images of the release before it; the changelog says when a release changes either.
The device does not load such an image, and the image stays on the board until
a save or a wipe. To go on, send your source again. Then type save. A
save before you send your source writes an empty program over the old image.
The web flasher erases an ESP32 board, so that board starts empty. A UF2 copy
to an RP2040 board and frothy flash keep the old image.
Update From v0.1.22
v0.1.23 changes the saved image and the profile hash of every board, and it has no migration. Do these steps in this order:
- Before you update, keep your source: the editor project or your
.frfiles.seeis not a full backup. - Flash v0.1.23.
- Install your libraries again.
- Send your program again.
- Set again the state that your program needs, for example cell contents,
records, and the
wifi.savecredentials after a browser flash. - Type
save.
If you updated without your source, do not type save. Do not use a wipe
command. When the flash did not erase the old image, the image stays on the
board, and v0.1.22 firmware can read it again.
Snapshot Format Boundary
The public contract is the model above: base image, overlay image, pointer-safe restore, and recovery to a usable prompt. The binary snapshot format is an implementation detail and may change while the pre-release runtime settles.
The important public constraints are stable:
- the base image is not duplicated into the snapshot
- overlay bindings are restored by symbol identity
- persistable code, text, records, and cells payload are serialized as values, not raw pointers
- native driver handles and live execution state do not persist; a slot holding
one is saved as
nil - incompatible snapshots must be rejected rather than half-restored
That is why persisted code can call gpio.write after restore without storing
the C function pointer. The restored overlay resolves gpio.write against the
boot-rebuilt base image.