Image and Persistence

Base versus overlay, rebinding, `save`, `restore`, `dangerous.wipe`, and `boot`.


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:

  • ok with no notice means the new overlay was made durable
  • notice: saved; handle values stored as nil (100) followed by its details and ok means the overlay was made durable, with the named slots written as nil
  • notice: not saved (13) followed by its details and ok means 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.