Reference
Container contract
What a slowapp container is given and what it must produce, under contract 3: command, environment, files, exit codes and limits.
The contract is the set of rules between a hub and an app’s container. Any image that follows it is a slowapp, in any language. The Python SDK implements all of it for Python apps; Slowapps in any language follows it by hand in Go.
Command
The hub starts the container with one of two commands:
job <name>runs one job. The container is removed when it exits.serveruns the app’s web page, listening on the Unix socket/run/sb/ui.sock.
While the page is running, the hub sends jobs to it as POST /_sb/jobs/<name> instead of starting another container.
Environment
| Variable | Value |
|---|---|
SLOWAPP_CONTRACT | 3 |
SLOWAPP_APP, SLOWAPP_JOB, SLOWAPP_RUN_ID | The app id, the job name and this run’s id |
SLOWAPP_REASON | Why the job runs: planner, schedule, manual, event or fixture |
SLOWAPP_NOW | The hub’s current time |
SLOWAPP_FOR | The time the cards will be shown |
SLOWAPP_DEADLINE | When the job must be finished |
TZ | The screen’s time zone |
SLOWAPP_MODE | live, or fixture in a test render |
SLOWAPP_FEEDS | The feeds wanted this run, as JSON: [{"feed":"prompts","slot":"right","w":452,"h":444,"inks":"k4","bpp":2,"key":"black"}] |
SLOWAPP_STATE | /data |
OPENBLAS_NUM_THREADS, OMP_NUM_THREADS | 1 |
Times are RFC 3339 with an offset. A feed declared with size = "slot" gets the slot’s real size in SLOWAPP_FEEDS.
Files
| Path | Access | Contents |
|---|---|---|
/data | read-write (read-only in a test render) | The app’s own folder, kept between runs |
/tmp, /cache | read-write, in memory | Scratch space, sized by the manifest |
/out | read-write | The run’s output, read by the hub after the job exits |
/in | read-only | settings.json, layout.json, and events.jsonl for an event job |
/run/secrets/<name> | read-only | One file per secret (live runs only) |
/run/sb | read-write | The web page’s socket and log |
Everything else is read-only.
Output files
/out/feeds/<feed>/deck.json the feed's cards, in order, and how long they are valid
/out/feeds/<feed>/cards/<id>.png one picture per card
/out/prompts.json questions (needs the prompts grant)
/out/prompts/<id>/<answer>.png the rest card for each answer
/out/prompts/<id>/ask.png an optional question card
/out/summary.json status lines and buttons for the hub's app card (any job)
/out/cache_key if equal to the last run's, the previous cards are kept
/out/timing.json {"next_change_at": "…"}: when the content next changes
deck.json:
{"cards": [{"id": "home", "title": "Today", "peek": "17° cloudy", "kind": "home"},
{"id": "hours", "title": "Next 24 hours", "kind": "detail", "keep_s": 600}],
"valid_from": "2026-10-01T13:15:00-07:00",
"valid_until": "2026-10-01T14:15:00-07:00"}
- The first card has
kindhome; every other card isdetail. - Card ids use lowercase letters, digits,
-and_. - Every card needs a
title.peekandkeep_sare optional. valid_untilmust be aftervalid_from.
A question with a question card fills its slot’s whole card area, and the strip shows only the answers. Without one, the question goes in the strip. The answer names are own.tap, spare.tap, own.hold and spare.hold; see Questions and answers.
Card pictures
Both the hub and slowd reject a card that breaks these rules:
- exactly the surface’s width and height;
- a PNG with 2-bit or 8-bit indexed colour;
- a palette that contains only the surface’s inks (for
k4: black, white, yellow and red) and no transparency; - at most 256 KB;
- at most 32 cards per feed, and at most 8 questions.
Exit codes
| Exit | Meaning |
|---|---|
| 0 | Store the output. |
| 3 | Unchanged: keep the previous cards. |
| 4 | Temporary failure: try again later, or once the network is back. |
| 5 | Needs configuration or sign-in: shown on the hub, not retried until a setting, secret or grant changes. |
| 64 | Contract error: the app broke these rules. |
| 137 | Stopped: out of memory or out of time. |
Any other code is a failure, retried with a growing delay.
Test renders
In a test render (SLOWAPP_MODE=fixture) the container has no network and no secrets, /data is the example’s read-only fixture, and SLOWAPP_NOW and SLOWAPP_FOR are fixed to the example’s time. The same fixture must always give the same pictures. Test without hardware has the details.