SDK

Apps region and containers

How a Kode Dot stores installed apps and kodeOS updates in flash: the A/B directory, the boot request, the mailbox, .kfw containers and the app manifest.

What it is

An app on a Kode Dot is a whole firmware, not a plugin. kodeOS writes its .bin into the flash region apps, and a small bootloader override jumps to it in place. Any reset brings the board back to kodeOS, because the app never touches the partition that says which kodeOS to start.

The module kdrv/apps is everything that handles those images:

PartFileJob
The apps regionkdrv_apps.cThe A/B directory, the allocator, image writes and checks, the boot request
The mailboxkdrv_apps.capp_pending: an image a computer drops with esptool, adopted at the next boot
kodeOS slotskdrv_slot.cfactory, ota_0, ota_1 and otadata: where kodeOS itself lives
Containerskdrv_kfw.cThe .kfw format and a streaming decoder in front of any of these destinations
The manifestkapp_desc.cThe display name and categories an app declares inside its image
Category overrideskapp_cat.cThe board's own category table, kept in NVS
The app folderkapp_data.cThe reader for an app's app.json

Two Active Objects use it. AO_AppMgr owns the directory, the images, the mailbox and the write to the free kodeOS slot (sdk/kaos/appmgr). AO_Boot owns the boot request and otadata (sdk/kaos/boot). The design of the region and the boot model are on internals/apps-region and internals/boot-model.

How it is connected

flowchart LR
subgraph aos["Active Objects"]
  am["AO_AppMgr"]
  boot["AO_Boot"]
end
subgraph drv["kdrv/apps"]
  kfw["kdrv_kfw<br/>decoder"]
end
subgraph flash["Flash destinations"]
  apps["kdrv_apps<br/>apps region"]
  slot["kdrv_slot<br/>free OTA slot"]
  mb["kdrv_apps<br/>app_pending"]
end
am --> kfw
kfw --> apps
kfw --> slot
kfw --> mb
boot --> apps
boot --> slot
apps -- "kapps_fmt.h" --> bl["bootloader override"]
class am ao
class boot ao
class apps store
class slot store
class mb store
Who writes where

kapps_fmt.h is the one header the driver and the bootloader share, word for word, so it includes nothing from ESP-IDF. It holds the magic numbers, the layout, the boot request and the rules both sides apply to them, as static inline functions. The bootloader and kodeOS are updated apart, so that layout never moves.

Why the directory has two copies

Flash only clears bits. An erase sets every bit of a 4 KB sector to 1, and a write can only change a 1 to a 0. Two designs follow:

The directory has two copies, A and B. kdrv_apps_dir_commit() never touches the copy believed valid. It writes the other one with gen one higher and a fresh CRC, waits for the write, and only then swaps its RAM copy. Inside a copy, the magic word is written last, so a cut leaves a sector that reads blank or corrupt and never one that looks whole. The commit refuses (KODE_DENIED) a directory that kapps_dir_sane() rejects, so the driver never seals a bad directory with a good CRC.

Init sorts each copy into one of four kinds. kdrv_apps_init() is idempotent: AO_Boot and AO_AppMgr both call it and whoever runs first pays the read. It also starts the category table first, so overrides are visible from the first manifest read.

KindMeaning
okMagic, version and CRC hold, count fits the array of 64, and every live entry is an image the region could boot
blankThe magic reads as erased flash
corruptAnother magic, a CRC that fails, or a copy that fails kapps_dir_sane()
foreignThe sector could not be read, or has the region's magic and another format version, as a different kodeOS would write

If either copy is foreign, the driver uses neither and formats nothing. It logs apps region directory unreadable or of another format, returns KODE_ERROR and leaves the region shut: every call that needs the partition refuses. A later init looks again. Otherwise the ok copy with the highest gen wins (A wins a tie). When neither is ok, the region is formatted: an empty directory with gen 1 goes to A and B is erased so it cannot pass for newer.

Placing and writing an image

The allocator is log-structured and next-fit. kdrv_apps_plan() touches no flash and keeps no state, so the host tests run it without a board.

    1. Round the image size up to 64 KB. An image that cannot fit in an empty region is refused (KODE_ERROR).
    2. Collect the live entries and sort them by offset.
    3. Start the search right after the newest install (highest seq), rounded up to 64 KB.
    4. Take the first gap that fits, looking from the search start to the end of the region, then from KAPPS_DATA_OFF up to the search start.
    5. If nothing fits, evict the entry named by replacing first, then the oldest live entry (lowest seq), add its extent to the search and go again. The order of evict[] is the order apps are freed in.

The eviction is first in, first out by install order. kodeOS has no notion of use to rank by. The one exception is replacing, the entry of the app the image is a new version of (KAPPS_NO_VICTIM for a new app): its old copy goes before any other app, because the install replaces it anyway. When evict[] holds it, replaced names it, and the caller does not count it as lost.

The plan also predicts one more victim, slot_victim: when all 64 directory slots would still be live after the evictions, AO_AppMgr removes the oldest remaining entry after the write. With replacing live there is none, because the new version takes its old copy's slot. Without it the plan is a prediction: an image with the same name as an installed app still replaces that app in its own slot, and only the written image tells. Replacing by name is AO_AppMgr's job. The driver's directory calls are generic: read, plan, commit.

A write session erases as it goes. kdrv_apps_write_begin(offset, size) opens a session. kdrv_apps_write() erases a 64 KB block only when the write reaches it, so the GUI keeps running between AO_AppMgr's 32 KB chunks.

The bytes reach flash through 2 KB of internal RAM (KDRV_BOUNCE_BYTES, borrowed per call from the heap). esp_flash_write() takes its fast path only from internal memory, and from PSRAM it programs 32 bytes at a time with the cache off.

Every image arrives in PSRAM. A 1.5 MB app from the card installs in about 8 s this way, against about 14 s straight from PSRAM, measured on a Kode Dot. If the heap has no 2 KB to lend, the write goes straight from the caller's buffer, slower and still correct. The mailbox and the kodeOS slot use the same helper (kdrv_bounce.h).

kdrv_apps_write_end() closes the session and runs kdrv_apps_verify() over exactly the bytes written, a plain esp_image_verify(). kdrv_apps_write_abort() only closes the session. The partial bytes stay in flash, unchecked and never entered in the directory.

Launching an app

The boot request says what the bootloader runs next. kdrv_apps_req_set() accepts only the states NEW and ALWAYS. It checks kapps_req_bounds_ok() first, so the refusal happens while there is still a console to explain it, then erases the request sector and writes the 20-byte record.

sequenceDiagram
autonumber
box "kodeOS"
  participant AM as AO_AppMgr
  participant B0 as AO_Boot
end
participant R as apps region
participant BL as bootloader
AM->>B0: BOOT_APP_RUN_SIG offset size mode
B0->>R: req_set: erase, write KRUN NEW or ALWAYS
B0->>B0: reset the chip
BL->>R: read request, check CRC and bounds
BL->>R: NEW becomes TRIED, ALWAYS becomes ARMED
BL->>BL: jump to the app image
Note over B0,R: next kodeOS boot: req_tried() yes, then req_clear()
One launch, from AO_AppMgr to the way home

NEW means one boot. ALWAYS is the sticky request that only mailbox adoption writes, so an uploaded sketch survives the Serial Monitor and the debugger. The bootloader marks it ARMED on the first boot, and from then on keeps it only across the resets in the table under Specifications. Back in kodeOS, kdrv_apps_req_tried() is how AO_Boot knows it is "back from the user app". kdrv_apps_req_armed() asks the same about the sticky state.

Adopting a mailbox image

The mailbox is checked in two phases. app_pending (6 MB at 0x1A00000, subtype 0x41) is non-executable, holds one image or one container at offset 0 and has no directory. A host tool writes the image with esptool, and a separate word at 0x5FF000 says what it is for (see Specifications).

    1. At boot, kdrv_apps_pending_present() reads one byte. 0xE9 means a bare image, and pending_walk_len() walks its header chain with esp_partition_read() to find its length. This is a length check only: it catches a torn or garbage upload, not a wrong hash. A walk that does not hold together consumes the mailbox at once, so a broken upload is not re-checked on every boot.
    2. Any other first byte may start a .kfw container, whose header states its own length. A container claiming more payload than the mailbox holds is discarded.
    3. When an install must evict an app, kdrv_apps_pending_verify() runs the full esp_image_verify() on the mailbox in place, so a corrupt upload cannot cost anyone an installed app. A container is not verified here.
    4. AO_AppMgr copies the image into the region, and kdrv_apps_write_end() runs the full check on the copy.

The walk does not trust the length a segment header states. It bounds the inputs of each addition, not its result, so a value near UINT32_MAX cannot wrap the offset around the partition.

The in-place check and the check on the copy need the 32-bit flash cache mapping (CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH), because both app_pending and the upper half of the region lie above 16 MB. Nothing boots on the strength of the length check alone.

kdrv_apps_pending_present() reports two lengths. img_len is what the mailbox holds, which is what a copy reads. decoded_len is what the image occupies once decoded, which is what an eviction plan is made for. They are equal for a bare image. A deflated container holds fewer bytes than it places.

The mailbox sink keeps a container verbatim, header and compression included, so reading it back checks it again. This is the one place where the decoder is not in the path, and kdrv_apps_pending_write_end() checks only the byte count. kdrv_apps_pending_consume() erases the first sector and the intent sector every time, so an intent flag never outlives its image.

Decoding a container

A container is decoded as a stream. kdrv_kfw.c holds the format, a decoder and the contract of a sink, the destination of a decoded image:

Sink callMeaning
begin(ctx, img_len)Open a session for img_len decompressed bytes, or 0 when the source cannot say. A sink that needs the length refuses here
write(ctx, data, len)Append one chunk. May block for as long as the flash takes
end(ctx)Close and verify
abort(ctx)Abandon. Always safe, even before begin()

Four sinks exist: kdrv_slot_sink() (with a size pre-check), kdrv_apps_sink() for the region, kdrv_apps_pending_sink() for the mailbox and kfw_buf_sink(), which fills a buffer the caller owns. The ESP32-C5 update uses the buffer sink, because a synchronous sink would block AO_Radio for a round trip per chunk.

The decoder is one static session with five states.

stateDiagram-v2
direction LR
[*] --> IDLE
IDLE --> SNIFF: kfw_begin
SNIFF --> RAW: first byte 0xE9
SNIFF --> BODY: 72 header bytes accepted
SNIFF --> DEAD: bad header, wrong target, wrong chip
RAW --> DEAD: sink error
BODY --> DEAD: overrun, bad deflate, sink error
RAW --> IDLE: kfw_end or kfw_abort
BODY --> IDLE: kfw_end or kfw_abort
DEAD --> IDLE: kfw_end or kfw_abort
The decoder's states

kodeOS slots and the app guard

The kodeOS slots. kdrv_slot.c is the only file that calls the esp_ota_* functions. "The slot" means whichever of ota_0 and ota_1 kodeOS is not running (from factory it is ota_0).

kdrv_slot_begin() opens the write with OTA_WITH_SEQUENTIAL_WRITES, so the erase is spread over the writes and the console does not freeze for seconds. kdrv_slot_confirm_self() must run at the top of every boot from an OTA slot: an unconfirmed image keeps esp_ota_begin() refusing, and no later update can start. The slot sink refuses an image bigger than the slot before the first byte, when the length is known.

An app is built with a flash guard. In a firmware built with CONFIG_KODEOS_BOOT_ROLE_APP, an app runs in place from a data partition, and ESP-IDF's write guard finds no running app partition and aborts every persistent write. kdrv_slot.c then wraps esp_ota_get_running_partition() to answer with a synthetic app partition covering the running image (subtype APP_TEST, so kdrv_running_is_factory() stays false) and esp_ota_get_next_update_partition() to answer NULL. The --wrap flags sit under the same role guard in the layer's CMakeLists.txt. kodeOS itself is built without them.

Manifest, categories and app folder

The manifest and categories. An app may declare a display name and up to four categories in a kapp_desc_t placed at image offset 0x120: IDF's own esp_app_desc_t starts at 0x20 and is 256 bytes, and a section placed right behind it lands at a fixed, computable address. kdrv_apps_desc() reads the first KAPP_PREFIX_LEN bytes of an image with one flash read and extracts both descriptors. A missing manifest is the common case: the launcher then shows the app as a loose row at the root, in no folder.

After every parse, kapp_cat_resolve() looks for a category the board set for the app, and an override beats the image, silence included. The board's table is one NVS blob in PSRAM, written only by AO_AppMgr.

The app folder. An app can carry an app.json and a 64 by 64 icon in a folder next to its image, under .kode/appsdata/<project> on either medium. kapp_data_parse() reads the file into fixed-size fields, allocates nothing and refuses the whole file on any fault, so a caller never draws half a folder. How an app gets its icon is on internals/app-icon.

Specifications

The region

ThingValue
Partition appsData, subtype 0x40 (KAPPS_PART_SUBTYPE), 0xC40000 to 0x1800000, size 0xBC0000 (11.75 MB)
Partition app_pendingData, subtype 0x41, fixed at 0x1A00000, 6 MB, non-executable
Byte orderLittle-endian; every field is a uint32_t. The magic 0x5050414B is stored as 4B 41 50 50, the letters "KAPP"
Image alignment64 KB (KAPPS_ALIGN, 0x10000): the MMU page, since an image maps for execution only from a page boundary
Directory capacity64 entries (KAPPS_MAX_ENTRIES)
Executing above 16 MBThe cache maps 32-bit addresses, so images run across the 16 MB mark

Offsets inside the region, counted from its start:

OffsetContents
0x0000 (KAPPS_DIR_A_OFF)Directory copy A, one 4 KB sector
0x1000 (KAPPS_DIR_B_OFF)Directory copy B
0x2000 (KAPPS_REQ_OFF)Boot request, one 4 KB sector
0x3000Reserved
0x10000 (KAPPS_DATA_OFF)First allocatable byte. Every image starts on a 64 KB boundary

The directory

kapps_dir_t is 1044 bytes in each copy. The CRC32 covers every byte before it. The valid copy with the highest gen wins.

OffsetSizeFieldMeaning
0x0004magicKAPPS_DIR_MAGIC, 0x5050414B
0x0044versionKAPPS_FMT_VERSION, 1
0x0084genGeneration; grows with every commit
0x00C4countEntries in use
0x0101024e[64]64 entries of 16 bytes
0x4104crcCRC32 of every byte above

Each entry (kapps_entry_t):

OffsetSizeFieldMeaning
0x04offsetFrom the region start; a multiple of KAPPS_ALIGN
0x44sizeExact image length in bytes
0x84seqInstall order, only grows: this is the FIFO
0xC4flagsBit 0 is KAPPS_ENTRY_VALID; bits 1 to 31 are reserved

The boot request

kapps_req_t is 20 bytes (KAPPS_REQ_LEN), frozen by a _Static_assert. The CRC32 covers magic to size only. The field state sits after the CRC, so the bootloader can advance it with a 4-byte write and keep the record valid.

OffsetSizeFieldMeaning
0x004magicKAPPS_REQ_MAGIC, 0x4E55524B ("KRUN")
0x044offsetRegion-relative offset of the image
0x084sizeExact image length
0x0C4crcCRC32 of magic to size
0x104stateOne of the words below
StateWordWritten by
EMPTY0xFFFFFFFFErased flash
NEW0xAAAAAAAAkodeOS: boot this once
ALWAYS0x55555555Mailbox adoption only: boot this across USB resets
ARMED0x00005555The bootloader, on the first boot of an ALWAYS request
TRIED0x00000000The bootloader, when it retires a request

Every step only clears bits: NEW to TRIED, ALWAYS to ARMED to TRIED. kapps_req_bounds_ok(offset, size, region_size) accepts an image only when it is at or past KAPPS_DATA_OFF, on a 64 KB boundary, not empty, not past the end of the region and fully inside it. The writer refuses a request that fails it, and the bootloader checks again, since flash is not safe only because a trusted writer wrote it once. The design is incompatible with flash encryption, which is not enabled.

An ARMED request survives these resets (kapps_req_cause_keeps(), a closed list, ROM numbering) while the mailbox is empty:

CodeCause
0x03Core software reset: OpenOCD's first reset
0x05Deep-sleep wake
0x0CCPU software reset: esp_restart(), a panic's reboot
0x16USB-JTAG: OpenOCD over USB
0x17USB-UART: a host opened the port
0x18CPU JTAG: OpenOCD's reset halt

Power-on (which BOT+LEFT is), the watchdogs, a brownout, a full mailbox and any other cause retire it to TRIED, and kodeOS boots.

The mailbox

OffsetContents
0x000000The image or container, up to KAPPS_PENDING_INTENT_OFF bytes
0x5FF000 (KAPPS_PENDING_INTENT_OFF)The intent word, in the last 4 KB sector

The word KAPPS_OS_INTENT_MAGIC (0x41544F4B, "KOTA") there means "adopt this as a kodeOS update, into the free OTA slot". Anything else, erased flash included, means "adopt this as an app", the default. A host tool writes the word separately, before the image. Nothing infers intent from the image's bytes. The constant is the contract with host tools that do not read the partition table, and a host test pins it to partitions.csv.

The container

A .kfw file is a 72-byte header and a payload. Everything in the header describes the decompressed image: the region allocator needs its length before the first byte is written, and the digest has to vouch for the image, since a digest of the payload would pass a correct transfer of a corrupt file.

OffsetSizeFieldMeaning
0x004magicKFW_MAGIC, 0x3157464B ("KFW1")
0x042hdr_version1
0x062hdr_len72
0x081target0 KFW_TARGET_OS (free kodeOS slot), 1 KFW_TARGET_APP (apps region), 2 KFW_TARGET_C5
0x091comp0 KFW_COMP_NONE, 1 KFW_COMP_DEFLATE
0x0A2flagsMust be 0
0x0C4payload_lenBytes after the header
0x104image_lenBytes after decompression
0x1432sha256Of the decompressed image
0x344crcCRC32 of the 52 bytes before it
0x3816nameNUL-terminated (KFW_NAME_MAX)

kfw_hdr_parse() checks the magic (a mismatch is a quiet KODE_ERROR: not a container), the CRC, the version and length, the target and compression ranges, flags == 0, both lengths non-zero, payload_len == image_len when uncompressed, and a terminated name. It fills its output only on KODE_OK. KAPPS_KFW_MAGIC in kapps_fmt.h is a second copy of KFW_MAGIC, held equal by a _Static_assert, so the bootloader can tell a parked container from erased flash without the decoder. The host-side producer is tools/mkkfw.py in the kodeOS repository.

The manifest

kapp_desc_t is 136 bytes at image offset 0x120 (KAPP_DESC_OFF), checked at compile time against KAPP_IDF_DESC_OFF plus sizeof(esp_app_desc_t). Its offsets below count from the manifest's start.

OffsetSizeFieldMeaning
04magicKAPP_DESC_MAGIC, 0x4450414B ("KAPD")
44versionKAPP_DESC_VERSION, 2: the struct's version, not the app's
832nameDisplay name, terminator included (KAPP_NAME_MAX)
4064categoryComma-separated list (KAPP_CATEGORY_MAX)
10432reservedEight words, zero

An app declares it once, at file scope: KODE_APP_MANIFEST("Kode Runner", "game,demo");. Static asserts refuse a name or list that is too long. The magic is the only thing that tells a manifest from whatever else the linker put at 0x120 in an app that declared none.

kapp_desc_parse() is pure and trusts nothing, since every byte may have come off a card. Bad magic or an unterminated string is KODE_ERROR, which means "unmanifested" and is never a failure to report. A version 1 manifest (88 bytes, KAPP_DESC_V1_SIZE, one 16-byte category) is read by its own bound and never by sizeof(kapp_desc_t). Any other version is KODE_ERROR. Three more readers work on an image prefix: kapp_project_name(), kapp_idf_desc() (project, version, build date and time) and kapp_image_chip(), which reads the chip id at byte 12 of the image header and keeps a P4 image out of the C5.

ConstantValueMeaning
KAPP_CATEGORY_ITEM_MAX16One category, terminator included
KAPP_CATEGORIES_MAX4Categories for each app
KAPP_CATEGORY_MAX644 times 15, plus 3 commas, plus the terminator
KAPP_CAT_ROWS_MAX64Apps that may carry an override
KAPP_PREFIX_LEN0x120 plus 136Image bytes a reader needs to hold

The list has one form everywhere: lowercase words separated by commas. kapp_cat_normalise() lowercases, trims spaces, drops duplicates, caps the list at 4 and logs anything it drops. kapp_cat_has() matches a whole item, so game does not match games.

The override table is one NVS blob, namespace kode, key app_cats:

OffsetSizeFieldMeaning
04magicKAPP_CAT_BLOB_MAGIC, 0x5441434B ("KCAT")
44version1: the blob layout
84countRows
1296 eachrowsproject[32] then category[64]; at most 64 rows, 6,156 bytes in all

kapp_cat_deserialise() accepts a table only if all of it is valid. Magic, version, count and length must agree, and every project[] and category[] must carry a NUL inside its array. Each row's category is normalised on the way in. The rows live in PSRAM, taken once by kapp_cat_init(). A change is made in RAM first and persisted after, so a failed NVS write leaves the new table in RAM until the next boot, and the caller hears the failure.

The app folder

app.json is one flat JSON object of string values, at most 1024 bytes (KAPP_DATA_JSON_MAX). Escapes are limited to \" and \\. No numbers, arrays or nesting.

KeyRule
kodeRequired, and must be "1"
nameDisplay name, up to 31 characters
categoryA list, normalised like a manifest's
versionUp to 15 characters
descriptionUp to 52 characters
iconA relative path of up to 40 characters, with no leading /, no \ and no .. component
color"#rrggbb"

A key the reader does not know is ignored whatever its length, so a newer tool's file still works on an older board. kapp_data_parse() returns KAPP_JSON_OK, _NONE (no file), _SIZE (over 1024 bytes), _SYNTAX (outside the subset or a field over its limit) or _REVISION (kode missing or not "1"), and zeroes its output on anything but OK. The icon file icon.k64 is 12288 bytes (KAPP_ICON_BYTES): 64 by 64 pixels of RGB565 (8192 bytes) followed by 64 by 64 of alpha (4096 bytes).

The calls

CallWhat it does
kdrv_apps_init(), _present(), _region_size(), _address()Open the region and describe it
kdrv_apps_dir()The working copy of the directory in RAM. Never NULL
kdrv_apps_dir_commit(d)Write the other copy, then swap. KODE_DENIED for an insane directory
kdrv_apps_plan(d, region_size, img_size, replacing, &plan)Where the image goes and who is evicted, the app being replaced first. Valid only for the directory it ran on, so apply the evictions and commit before writing
kdrv_apps_write_begin(off, size)KODE_DENIED if the region is absent or a session is open. KODE_ERROR for an offset below KAPPS_DATA_OFF, a misaligned one or an extent past the partition
kdrv_apps_write(), _write_end(), _write_abort(), _sink()Stream the image in, verify it, or walk away. The sink needs kdrv_apps_sink_ctx_t{offset} and refuses img_len == 0
kdrv_apps_read(off, buf, len)Read from the data area. No session. KODE_ERROR below KAPPS_DATA_OFF or past the partition
kdrv_apps_desc(entry, &desc, &manifest)One flash read, both descriptors, then the override
kdrv_apps_verify(off, size, &len)esp_image_verify() at the region's absolute address; optionally the true image length
kdrv_apps_erase_header(off)Erase the first 4 KB of an extent: the cheap uninstall
kdrv_apps_req_set(), _req_tried(), _req_armed(), _req_present(), _req_clear()The boot request
kdrv_apps_pending_present(), _verify(), _read(), _os_intent(), _consume()The mailbox
kdrv_apps_pending_write_begin(), _write(), _write_end(), _write_abort(), _sink()Write it from kodeOS. Capped at KAPPS_PENDING_INTENT_OFF bytes
kfw_begin(), kfw_feed(), kfw_end()One decode session. kfw_begin takes the sink, its ctx, the wanted target, the source length, a header callback
kfw_abort(), kfw_abort_owned(ctx), kfw_busy(), kfw_produced()Abandon, ask, count the image bytes handed to the sink (not the bytes fed in)
kfw_hdr_parse(), kfw_sha256(), kfw_crc32(), kfw_selftest()Header check, digest, CRC, and the self-test behind test component=kfw
kdrv_slot_present(), _size(), _address(), _desc(), _has_image(), _info(id, &info)The free slot, and any of the three kodeOS partitions by id
kdrv_slot_begin(), _write(), _end(), _abort(), _sink(), _erase()Write the free slot. erase clears its first 64 KB
kdrv_slot_confirm_self()Confirm the running image if it is still PENDING_VERIFY
kdrv_otadata_blank(), _clear(), _select_slot(), kdrv_slot_state()Read, reset and point otadata. select_slot verifies the image first
kdrv_slot_coredump(), kdrv_slot_coredump_reason()Whether a core dump waits in flash and whose it is
kdrv_running_info(), _desc(), _is_factory(), kdrv_reset_reason()The running partition and the chip's reset reason

The partitions behind the slot driver:

PartitionOffsetSize
otadata0x1B0000x2000
factory0x200000x400000 (4 MB), the rescue kodeOS, reachable by cable only
ota_00x4200000x400000 (4 MB)
ota_10x8200000x400000 (4 MB)
coredump0xC200000x20000

The files

FileWhat it does
include/kapps_fmt.hThe on-flash format and the rules shared with the bootloader. No ESP-IDF
include/kdrv_apps.h, src/kdrv_apps.cThe region, the allocator, the write session, the boot request, the mailbox
include/kdrv_slot.h, src/kdrv_slot.ckodeOS slots, otadata, self-confirm, the core dump query, the app-role flash guard
include/kdrv_kfw.h, src/kdrv_kfw.cThe container header, the streaming decoder, the sinks contract, the buffer sink, the self-test
include/kapp_desc.h, src/kapp_desc.cThe manifest, the image-prefix readers, KODE_APP_MANIFEST
include/kapp_cat.h, src/kapp_cat.cCategory lists and the NVS override table
include/kapp_data.h, src/kapp_data.cThe app.json reader
src/kdrv_bounce.hFlash writes through 2 KB of internal RAM. Private
src/kdrv_desc_fill.hIDF's app descriptor into kdrv_slot_desc_t, each string read with its array's width. Private

Gotchas

Example code

An app that declares its name and category, which is the part of this layer an app author writes. Install it, and the launcher shows "Hello" in the folder examples.

/*
 * manifest.c - 2026-10-28 - from Kode with love
 *
 * Declares a display name and a category in the app's image, at offset
 * 0x120, then logs a line. After the install the launcher lists the app as
 * "Hello" in the folder "examples", and the log shows: hello from the apps
 * region.
 */
#include "kode.h"
 
#define TAG  "hello"
 
/* What the launcher shows, and the category it files this app under. */
KODE_APP_MANIFEST("Hello", "example");
 
/* Bring the system up, say hello, then idle so the console stays up. */
void app_main(void)
{
    kode_init();
    KLOG_N(TAG, "hello from the apps region");
 
    while (1) {
        kode_delay_ms(1000);
    }
}

Compiled against the SDK with ESP-IDF v6.1 on 2026-10-28. TBD: a run on a Kode Dot.

In the SDK