Memo can update itself from inside the app. When it starts it asks what is new; if
there is an update a window opens with the versions, the download size and a short
note, and one click ("Update now") does the rest. Engine files (llama.cpp,
whisper.cpp, the Subscriptions connector) can be refreshed on their own, without a
new Memo release.
This page is for people who ship Memo. Design record: docs/plans/PLAN_auto_update.md.
| Situation | Window |
|---|---|
| A newer Memo exists and this install can update itself | versions, size, note → Update now / Later / Skip this version |
| …and the release is marked required | same, without "Skip this version" ("Later" stays) |
| A newer Memo exists but this install cannot update itself (AppImage, deb, Docker, source checkout, a platform with no published build) | note + Open download page |
| Only newer engine files exist | list of components (b9441 → b11456), applied one at a time |
| The previous attempt failed and was rolled back | a line above the buttons saying so; another try is offered |
"Skip this version" is remembered per device; a newer release, or a required one,
asks again. "Later" asks again next launch.
Linux / macOS (script installs, ~/.memo). The backend downloads the release,
checks its SHA-256 against a signed manifest, unpacks it into <install>/.update/,
starts the memo-updater found inside the new release and exits. The updater waits
for the old process to be gone, renames the old app files into a backup, renames the
new ones in, relaunches Memo through its launcher script and waits for the backend to
answer with the expected version. If it does not (or the launcher dies), the new
files are removed, the old ones restored and the old Memo relaunched; the reason is
left in update-failure.txt and shown at the next start. config/, .env and
everything under data/ (models, memory, sessions, providers) are never touched —
the list of replaced items is updater.SwapItems, which mirrors get-memo.sh.
Progress is logged to <install>/update.log.
Windows. The backend downloads memo.exe (the Inno Setup program), verifies it,
starts it through the shell (so the normal UAC prompt appears) and exits. The person
finishes in the installer; it upgrades in place (stable AppId, data in ProgramData).
Engine components. New files are downloaded into <data>/components/binaries/<os>/<flavor>/
— never into the install (on Windows that is Program Files and needs an administrator)
— and searched before the bundled copy. The engine using the folder is stopped,
swapped, started again and the new binary is run (--version must report the
manifest's build). On failure the folder is removed again, so the bundled engine is
used. Which flavor is used is still decided by the bundled tree and the engine mode:
a downloaded cpu engine can never move a machine that runs the amd engine to cpu.
After an app update that bundles the same or a newer engine, the downloaded folder is
deleted at startup.
version.json (version-zeta.vercel.app){
"version": "V4.7.0",
"mini_version_note_tr": "kısa not",
"mini_version_note_en": "short note",
"required_update": false,
"components": { "llama": "b11456", "whisper": "1.9.0", "cliproxy": "v8.0.26" }
}
Releases before the updater read only version; the rest is additive. The beacon is
not signed and is only a trigger: it names which version to ask for. URLs, sizes
and SHA-256s come from signed manifests, so a taken-over beacon can withhold or
re-announce an update but cannot make Memo run a file you did not sign. components
values are validated (they end up in URLs); required_update only hides the skip
button; notes are shown as plain text, cut to 600 characters.
A component-only release does not change version, so older apps show nothing.
internal/updater/pubkey.go). TheMEMO_UPDATE_SIGNING_KEY Actions secret and in~/.config/memo-release/update-signing.key on the maintainer's machine. A build with.., hard links, devices and links that resolve outside the target — itx/a -> .. followed by x/a/l -> ../../y.CI does the heavy lifting on a real release tag (vX.Y.Z matching the version
file):
LLAMA_VERSION / WHISPER_VERSION into the engine folders frominternal/components/ENGINES.txt and checks them; memo-updater is built into thescripts/publish_update_manifest.shupdate/app/<X.Y.Z>/<platform>.json(.sig). A checkpoint tag thatversion publishes nothing. The signature is checked against thescripts/verify_update_manifest.sh — it checks every platform's manifest,version.json:Engine-only release (no new Memo):
scripts/stamp_engine_versions.sh stamp --llama b11456 <dir> # markers inside the folder
scripts/publish_component.sh --dry-run --requires-app 4.7.0 llama b11456 linux/cpu=<dir> …
scripts/publish_component.sh --requires-app 4.7.0 llama b11456 linux/cpu=<dir> …
# then set "components": {"llama": "b11456"} in version.json
A folder is replaced as a whole, so publish it complete: linux/cpu holds llama.cpp
and whisper.cpp (and memo-lora-train) linked to the same libraries — seeAGENTS.md "llama.cpp upgrade traps". Folders you leave out are not updated (this is
how NVIDIA stays on an older llama.cpp). --requires-app keeps an old app from being
handed an engine it cannot drive (for example a changed flag).
ShellExecute) andrun_memo.sh next to the backendget-memo*.sh, get_memo_arm.sh, update.sh) refresh older engine folders while they install: after copying binaries/ they read the same beacon, compare each Linux engine folder's marker, download the published folder (SHA-256 from the manifest, no ../links, marker must match, the new binary must start) and swap it whole, keeping the old one on any failure. Only folders this machine can use (cpu, cliproxy; amd/nvidia with that GPU), never a folder without a marker, never fatal; MEMO_SKIP_ENGINE_REFRESH=1 skips it. Source of truth: scripts/engine_refresh.sh (embedded verbatim in the installers, kept identical by internal/components/installer_refresh_test.go). It does not check the Ed25519 signature and ignores requires_app.ENGINES.txt.<install>/update.log — what the updater did. <install>/update-failure.txt — the.update/ is left behind only when even the rollback failed; it may hold the onlyMEMO_TELEGRAM_API_BASE): MEMO_UPDATE_BEACON_URL,MEMO_UPDATE_BASE_URL, and — only together with a loopback base URL —MEMO_UPDATE_PUBKEY.GET /api/update/check[?force=1] and GET /api/update/status are ambient;POST /api/update/apply {"kind":"app"} / {"kind":"component","component":"llama"}
is admin-only and answers 202 at once.