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 buffer
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
.
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: Lets you interrupt startup before restore and boot finish, so bad
saved state does not trap the device in a broken loop.
Example:
Press Ctrl-C during the safe-boot window, then inspect `boot` or wipe the overlay.
Typical recovery flow:
1. Connect to the device.
2. Press Ctrl-C during the safe-boot window.
3. Run `see boot`.
4. Fix the bad slot, or run `dangerous.wipe`.
A Saved Image Belongs To One Release
A saved image records the release that wrote it. A Frothy release that adds or
changes a word changes the shape of the image, so a device refuses an image
another release wrote. It reports other release at boot and from restore.
Nothing is damaged when this happens. The board is healthy and the image is
intact; this firmware cannot read it. Run save to write an image this release
can read, or wipe-user to clear the saved state.
The web flasher erases the whole board, so a board you flash from the browser never shows this. You see it when you flash by another route and leave the old saved data in place.
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.