Letta Code — the coding agent with persistent memory that learns across sessions — running on Allwinner H3 / armv7l (Smart Pi One, Yumi SmartPad), a platform none of its official channels (desktop app, Nix flake) build for.
It runs natively (no emulation): the @letta-ai/letta-code npm package is one
plain-JavaScript bundle, so all it needs is a Node ≥ 22.19. Agents, memory and
provider credentials stay on the pad (local backend, no Letta Cloud account
required). Sign in with an Anthropic subscription (OAuth, works over SSH) or
any API key; full interactive agent, full tool use.
╭──────────────────────────────────────────────────────────╮
│ Letta Code 0.31.12 · armv7l │
│ › _ │
│ │
│ letta · letta --new · letta -p "…" for one-shot │
╰──────────────────────────────────────────────────────────╯
curl -fsSL https://raw.githubusercontent.com/Yumi-Lab/letta-code-smartpi/main/install.sh | bashThe installer always fetches the newest version published on npm (re-running it
is the update path). To pin or roll back: LETTA_CODE_VERSION=0.31.12 bash install.sh.
Then open a new shell (the installer adds ~/.local/bin to your PATH) and connect a
model provider — the first run offers it, or do it from the terminal:
letta # setup screen → Anthropic subscription (OAuth) or key
letta connect anthropic <api_key> # non-interactiveThe Anthropic subscription flow prints a URL and asks for the code back — open the
URL on any browser, nothing has to run on the pad. (The OpenAI/ChatGPT flow instead
listens on localhost:1455: use ssh -L 1455:localhost:1455 pi@pad and a browser
on your machine.)
letta -p runs on the local backend with whatever provider is connected, or with a
key in the environment — no login step:
ANTHROPIC_API_KEY=sk-ant-… letta -p "summarise this repo"| Command | Purpose |
|---|---|
letta |
Full interactive agent — resumes the last conversation of this project |
letta --new |
New conversation (--new-agent for a fresh agent) |
letta -p "question" |
One-shot, headless |
letta --backend cloud |
Use a Letta Cloud account instead of the local backend |
letta connect <provider> … |
Connect a provider from the terminal (anthropic, openai, codex, ollama, lmstudio, llama-cpp, bedrock…) |
letta memory … / letta agents … |
Memory filesystem and agent subcommands |
LETTA_CPUS=0,1 letta … |
Limit the running agent to 2 cores (default: all 4) |
LETTA_DAEMON=1 letta -p … |
Queue headless jobs, 2 live at a time (see below) |
letta-check-update |
OTA probe — one JSON line |
letta-daemon-status |
Daemon probe — one JSON line |
Local backend. The launcher exports LETTA_LOCAL_BACKEND_EXPERIMENTAL=1: state
lives in ~/.letta/lc-local-backend/ (each agent's memory is a git repository),
and headless runs do not require a Letta Cloud key — without the switch, letta -p
exits with "Missing LETTA_API_KEY". Override per launch with --backend cloud, or
globally with LETTA_LOCAL_BACKEND_EXPERIMENTAL=0. Local mode keeps persistent
memory, self-improvement, subagents and skills; Slack/Telegram/Discord channels,
chat.letta.com remote access and multi-computer need a Letta server.
Cores. LETTA_CPUS pins the running agent (taskset + nice -n 5), the
runtime twin of KIMI_CPUS on
kimi-code-smartpi and
CLAUDE_CPUS on
claude-code-smartpi. Default:
all 4 cores. LETTA_CODE_BUILD_CPUS does the same for the install step.
Warm start. The launcher exports NODE_COMPILE_CACHE=~/.cache/letta-code/v8, so
Node 22 caches the V8 bytecode of the 34 MB bundle: the compile happens once (the
installer primes it), every later launch skips it. The cache key includes the Node
version and architecture, so a Node upgrade invalidates it automatically.
Daemon (batch work). LETTA_DAEMON=1 routes headless runs (-p) through a
small job daemon: a FIFO queue with a semaphore of LETTA_MAX_CONCURRENT (default
2) live runtimes. A queued job costs ~0 RAM until a slot frees, which is what
lets a 1 GB board accept a batch of agent jobs without swapping. The daemon
lazy-starts on the first job and exits after LETTA_IDLE_MS (default 5 min) of
idle — no systemd unit, no boot cost. Each job carries the calling shell's
environment (provider keys included). The interactive TUI never goes through it.
LETTA_DAEMON=1 letta -p "review the last commit" # queued if 2 are already running
letta-daemon-status --json # {"daemon":true,"running":2,"max":2,"queued":3}
LETTA_MAX_CONCURRENT=3 … # only after checking `free -m` — see Status- Check:
letta-check-updateprints one JSON line —{"cli":"letta","installed":"0.31.12","latest":"0.32.0","update_available":true}. This is the probe the Yumi AI Gateway console polls for its update badge. - Update: re-run
install.sh— that IS the updater. It resolves the newest version from the npm registry, exits as a no-op when you are already current, and reinstalls the launcher, shims and probes either way. ⚠️ Never runletta update. Upstream's self-updater runsnpm install -gin the background at startup; the launcher disables it (DISABLE_AUTOUPDATER=1) because this board updates throughinstall.sh.- Privileges: everything lives under
$HOME— no sudo after the first install (which may install Node,git,ripgrep,libatomic1andearlyoom): the gateway service user updates unprivileged.
- Letta Code's desktop app and Nix flake cover x86_64 and aarch64 only → dead end on armv7l.
- But the CLI is published on npm as
@letta-ai/letta-code, and that package is plain JavaScript:bin→letta.js(34 MB ESM bundle with a Node shebang). Its native pieces aresharp(an armv7 prebuilt exists upstream; npm picks it) andnode-pty(no Linux prebuild anywhere, lazy-loaded, only needed for interactive PTY shells). - So the installer needs a Node satisfying
engines: >=22.19.0withnode:sqlitebuilt in. Debian trixie ships Node 20 → it installs Node 22 armv7l from nodejs.org (into/usr/localwhen privileged — shared with the other*-smartpiCLIs — otherwise into~/.local/lib/nodejs). - The package is installed non-globally into
~/.local/lib/letta-codewith--ignore-scripts(no compiler, no telemetry ping), then upstream's own post-install patch script (terminal flicker fixes for ink) is run by hand. - A launcher fixes the two switches this board needs — local backend on,
self-updater off — plus core pinning and the compile cache;
earlyoomcompletes the safety net.
Details and evidence: docs/METHODOLOGY.md.
- Smart Pi One / SmartPad, Armbian or DietPi (Debian trixie), 1 GB RAM — see the RSS figures below before running more than one agent at a time.
- ~350 MB free on the SD card: 315 MB of package tree after pruning, ~25 MB Node when not already present, 3 MB of compile cache.
- Network at install time (npm registry, nodejs.org, apt) and at run time (the model provider only, in local mode).
- Optional, for interactive PTY shells:
LETTA_CODE_WITH_PTY=1 bash install.shbuildsnode-ptyfrom source (build-essential,python3-dev, ~3 min emulated).
Validated in a real armv7l userland (Docker linux/arm/v7, qemu-arm), not yet on
the pad. test/install-armv7-docker.sh runs the real installer and asserts:
| Check | Result |
|---|---|
install.sh end to end on armv7l (apt, Node 22, npm, ink patches, cache) |
OK — 12 min emulated, 315 MB on disk, sharp armv7 prebuilt selected automatically |
letta --version, --help, require("sharp"), node:sqlite |
OK |
| headless local turn with an invalid Anthropic key | reaches api.anthropic.com, returns the provider's 401 as a structured error — the whole pipeline minus a valid key |
same turn through the job daemon, then letta-daemon-status |
OK |
letta-check-update right after install |
installed = latest, update_available: false |
second install.sh run |
no-op on the npm step |
node-pty from source |
builds and loads (199 s emulated) |
Measured under emulation (pad numbers will differ, mostly in elapsed time):
| Run (through the installed launcher, compile cache primed) | Elapsed (emulated) | Max RSS |
|---|---|---|
letta --version |
24 s | 290 MB |
| one headless turn | 52 s | 329 MB |
The RSS is what a 1 GB board cares about: about twice a Claude Code or Kimi Code runtime, hence the daemon default of 2 concurrent jobs. Not yet measured on hardware: launch time, TUI behaviour over a real TTY, RSS on the pad, thermals.
shellcheck -x install.sh lib/letta-release.sh bin/letta-check-update test/install-armv7-docker.sh
node --check shim/letta-daemon.mjs shim/letta-client.mjs bin/letta-daemon-status
test/install-armv7-docker.sh # ~15 min: real install + smoke in linux/arm/v7
LETTA_SMOKE=0 test/install-armv7-docker.sh # installer + layout only- claude-code-smartpi — official Anthropic Claude Code, native (Bun bundle extracted and run under Node).
- kimi-code-smartpi — Moonshot Kimi Code CLI, the same npm + Node 22 route as this repo.
- codex-cli-smartpi — OpenAI Codex CLI (Rust), native cross-build or emulated.
- grok-cli-smartpi, vibe-cli-smartpi, kimi-cli-smartpi.
This repository (installer, launcher, daemon, probes): MIT — see LICENSE. Letta Code itself is Apache-2.0, © Letta; it is fetched from npm at install time and is not redistributed here.