Hardware
Nexting Devices
Nexting Devices SDK · Experimental 0.4 · September 2026
1. What This Is
Nexting Devices is an open device SDK. Your hardware speaks one small BLE protocol to the Nexting App — and nothing else. The App and the Nexting cloud already do the hard part: they translate and forward Claude Code, Codex, Grok, and Cursor sessions in real time. Your device runs no agent code, holds no credentials, and works with those agents from day one.
Your device
any BLE hardware
Nexting App + cloud
translate · forward
CC · Codex · Grok · Cursor
your agents
You build the hardware; Nexting provides the app, the server, and the agent integrations.
2. What a Device Can Do
Every operation the SDK exposes to a device, for each agent. The App normalizes the differences between Claude Code, Codex, Grok, and Cursor — the device sees one identical surface. Overview first; each capability is specified in full below.
| Capability | Device operation | Claude Code · Codex · Grok · Cursor | Status |
|---|---|---|---|
| Permission approval | Show a request with summary + countdown; answer Allow / Deny | ✓ | Shipped · approval/1 |
| Agent status display | Render up to 8 agent slots × 6 states, optional label | ✓ | Shipped · status/1 |
| Option navigation | Move up/down through multi-option prompts, confirm a choice | Host adapter | Shipped · navigation/1 |
| Command keys | Dedicated keys: approve, decline, fork, send, switch session | Host adapter | Shipped · keys/1 |
| Screen content | Receive conversation text to display on device screens | Host adapter | Shipped · text/1 |
| Rotary control | Scrub through option levels pushed from the App | Host adapter | Shipped · rotary/1 |
| Push-to-talk voice | Hold a key, talk to the agent | Host adapter | Shipped · voice/1 |
| Remote configuration | Key maps and lighting downloaded from the App, no reflash | Host adapter | Shipped · config/1 |
| Usage display | Show model and bounded token/context counters | Host adapter | Shipped · usage/1 |
2.1 Permission approval · approval/1 (shipped)
What it solves: mid-task, the agent needs your decision — a Claude Code permission prompt, a Codex approval, a Grok or Cursor ACP permission. The App turns that two-option request into a present message; you answer with one key press on the device, no phone required.
The protocol messages (all four, plus errors):
| Message | Direction | Fields | Meaning |
|---|---|---|---|
| present | App → device | id (1–64 char request ID) · sum (≤ 240-byte summary) · opt, always ["allow","deny"] · ttl (1–300000 ms countdown) | A request awaits your answer; a new present replaces the old one |
| answer | device → App | id · ch ("allow" or "deny") | Your answer; only valid for the request currently displayed |
| resolved | App → device | id · r (answered / expired / cancelled / replaced) | The request is finished; the device clears display and retry state immediately |
| error | both ways | id · code (bad_message / message_too_large / unsupported_version / unsupported_profile / unknown_request / not_authorized / busy) | Protocol-level error; silently dropping malformed input is also legal |
App → device {"v":1,"t":"present","id":"3bb7","sum":"Allow git push?","opt":["allow","deny"],"ttl":30000}
device → App {"v":1,"t":"answer","id":"3bb7","ch":"allow"}
App → device {"v":1,"t":"resolved","id":"3bb7","r":"answered"}Device-side rules:answer only the currently visible request; after sending an answer, wait for resolved — if it does not arrive within one second you may resend the identical answer (never change the choice); duplicate answers are idempotent by request ID and the first hardware choice stays locked. One pending request at a time — prompts with more than two options (like Claude Code’s AskUserQuestion) are not sent to devices today; they belong to option navigation below.
2.2 Agent status display · status/1 (shipped)
What it solves:you run several agents at once and want to know who is working, who is stuck, and who is done — without opening the App. The App pushes every agent session’s live state to the device, which renders it on LEDs, key backlights, or a screen.
The six states (every slot is in exactly one):
| State | Meaning | Typical rendering |
|---|---|---|
| idle | Session idle, nothing running | LED off / grey |
| thinking | Reasoning — waiting on model output, planning | Slow blink / blue |
| working | Executing — editing files, running commands | Solid / green |
| complete | This round of work is done | Single flash / cyan |
| needs_input | Blocked, waiting on a human (permission, question) | Fast blink / orange |
| error | Something failed, needs a look | Solid / red |
{"v":1,"t":"status","agents":[{"slot":0,"state":"thinking","label":"fix login bug"}]}Device-side rules: up to eight anonymous slots (0–7); every message is a full replacement — a slot missing from the array is empty, an empty array clears everything; screened devices additionally receive an optional ≤ 64-byte label. Status is volatile: disconnect, reboot, or re-pair and every slot clears — an indicator never shows a stale agent. The App only sends status to devices that declare statusSlots ≥ 1; a device that did not declare it receives nothing, and sending it any traffic is not expected.
2.3 Option navigation · navigation/1
Claude Code’s AskUserQuestion, Codex multi-option confirmations, Cursor questions — prompts with more than two options. The App pushes the option list and the current highlight; the device moves the highlight with keys or a stick and confirms with a select key. The frozen messages are downlinknav_present / nav_resolved and uplink nav_move / nav_select. Sequence numbers reject replay and reordering.
2.4 Command keys · keys/1
Each physical key maps to one agent command: approve (same as answering Allow), decline, fork (branch a new session from the current one), send(inject a preset text into the agent’s input), switch session (rotate through your active agent sessions; status lights follow). Direction: uplink key events; downlink key labels and backlight state (which session is active). The Wire messages are keymap and key_event. The key event is intentionally generic: the trusted Host Agent adapter owns the final meaning and authority.
2.5 Screen content · text/1
An approval summary is only 240 bytes; screened devices want more. The text content capability lets the App push longer conversation excerpts for the device to scroll. The App still minimizes: only necessary conversation text leaves it — credentials and internal identifiers never do. The text message carries bounded plain text for a declared display channel, never markup or arbitrary Agent payloads.
2.6 Rotary control · rotary/1
A rotary encoder scrubs through levels — model tiers, approval strictness, session selection. Direction: uplink rotation deltas; downlink the level set and the current level, which the device can display. The frozen messages are rotary_map, rotary_event, and rotary_press; no internal model or session ID is sent to the device.
2.7 Push-to-talk voice · voice/1
Hold a key on the device and talk; release to send. The Host captures audio and injects the transcription into the current Agent session.voice_event carries start/stop/cancel and voice_state carries progress. Audio and transcripts never cross this profile; microphone permission and capture stay on the Host.
2.8 Remote configuration · config/1
Key maps, lighting schemes, and display preferences download from the App — no reflash. Configurations are versioned; a config that fails validation leaves the previous one untouched. config is applied atomically and config_result reports applied or rejected.
2.9 Usage display · usage/1
usage carries a bounded model label and informational token/context counters; usage_clear removes them. These values drive a display and are never treated as billing authority.
2.10 SDK API surface
| Layer | Implementation | Key API |
|---|---|---|
| Device SDK | Portable C99 | nexting_device_decode / encode · nexting_device_stream_push · nexting_device_status_on_message |
| Reference | JavaScript | Readable protocol implementation pinned to the shared vectors |
| Test vectors | JSON | approval-v1.json · status-v1.json — every language validated byte for byte |
3. How to Integrate
3.1 Device side: the C99 core
Device firmware is one loop: bytes in → decode → render; user action → encode → bytes out. The C99 core uses fixed buffers, zero heap, and no OS dependency — it ports to any RTOS:
// Init: stream parser + status state machine
nexting_device_stream_init(&stream, storage, sizeof storage);
nexting_device_status_init(&status);
// ① BLE bytes arrive → feed the stream parser; it calls back per frame
nexting_device_stream_push(&stream, bytes, len, on_message, ctx);
// ② In the callback: decode → render by type (LED / screen / keys)Malformed, oversized, or unknown-type input fails closed inside the SDK — your rendering code never sees bad data. The Zephyr reference firmware is this loop, complete, shared across all four boards.
3.3 Authorization flow
A device does nothing out of the box. The user explicitly authorizes it in the Nexting App → an encrypted, bonded link is established → only then does any traffic flow; revoking authorization in the App stops all traffic immediately. Device Info is readable before authorization for compatibility discovery only — reading it grants nothing. One contract serves two identity tiers: Nexting first-party products (PIN, Ring) carry production identity; third-party and DIY devices run under explicit authorization and are labeled as such in the App.
4. Build Your First Device
1. Pick a board. Nordic nRF52840 DK, Seeed XIAO nRF52840 / Sense, XIAO ESP32C3, or XIAO ESP32S3 — one Zephyr application covers all four, Build verified in CI (Zephyr 4.3.0 / SDK 0.17.4).
2. Flash the reference firmware. Out of the box it is a two-button approval pad.
3. Authorize it in the Nexting App and answer a real request from your own agent.
4.1 MultiPad USB CDC developer path
ILX MultiPad is a separate public STM32F103VET6 USB HID + CDC path, not the same transport as the BLE reference boards above. The public adapter reuses the portable C99 SDK and carries the same approval/1 / status/1 frames over CDC; it does not put the App, Agent, or credentials in firmware and does not automatically enroll in the Nexting App over USB.
| Repository asset | Use | Current evidence |
|---|---|---|
| firmware/multipad | adapter, CMake test, Device Info template | host test passing |
| tools/multipad-cdc-smoke.py | AA BB CC echo and present sender | adapter firmware required |
| tools/flash-multipad.sh | backup and explicit write | does not enter bootloader |
| docs/multipad-usb.md | Module/FPC and GitHub tutorial | physical evidence pending |
Whether Type-C can flash this unit depends on the opened PCB: the module variant exposes the upstream serial switch and BOOT/RESET buttons; the FPC variant needs SWD/J-Link. Back up the original flash first and use the repository script; never treat a normal CDC port as a bootloader.
4.2 First case: ILX MultiPad integration
This is the first complete public hardware case. Pin the upstreamiLx11/multi-pad firmware, build it unchanged once, then add the public C99 core and nexting_multipad_adapter as a thin CDC binding. The first stable mapping is deliberately small: matrix key 1 answers Allow, matrix key 2 answers Deny, and the remaining keys and rotary controls keep their upstream behavior until their own profiles are released.
| Step | What you do | What proves it |
|---|---|---|
| 1 · Pin the source | Checkout upstream commit 78c1ee5… and keep the original HEX | Original hash recorded |
| 2 · Add the adapter | Route JSON CDC frames before the legacy AA BB xx branches | C99 + CMake tests pass |
| 3 · Choose the writer | Module uses serial only if SERIAL/BOOT/RESET are present; FPC uses ST-Link/J-Link SWD | PCB path identified |
| 4 · Verify one flow | present → Allow/Deny → resolved, then expiry and disconnect | Same request ID and clean state |
The full source-first walkthrough, build separation, pin evidence, and rollback rules live in the public GitHub guide at docs/cases/multipad-first-case.md. The existing leden.hex from upstream is not a Nexting image; never flash it as the adapted firmware.
4.3 AhaKey X1 BLE case
The AhaKey X1 is a shipping BLE vibecoding keyboard — four keys, an approval lever, an LED strip, an OLED, and a voice-input use case. The current public Devices SDK is Experimental 0.4 and includes the AhaKey directory and device-audio profile. Production PCM binding, target build, mobile conformance closure, and physical-board verification are still pending.
| X1 hardware | Nexting interface | Behavior |
|---|---|---|
| Keys F18 / F19 | approval/1 | Allow / Deny the pending request |
| Keys F13–F16 + lever | keys/1 slots 0–3 and 7 | Host-mapped actions |
| LED strip | status/1 | Agent state as IDE light |
| OLED 160×80 | text/1 | Request and status text |
| Device microphone + voice key | device-audio/1 | Device PCM → encrypted BLE → App; no phone-mic substitution |
The AhaKey X1 guide explains the current 0.4 contract and the evidence required before the AhaKey files can be called board-verified.
5. Protocol Reference
BLE transport
| Role | UUID | Properties | Direction |
|---|---|---|---|
| Service | 6EADC0DE-0001-4A21-9C5E-1B7F3D9E42A0 | Primary | — |
| Downlink | 6EADC0DE-0002-4A21-9C5E-1B7F3D9E42A0 | Write | App → device |
| Uplink | 6EADC0DE-0003-4A21-9C5E-1B7F3D9E42A0 | Notify | Device → App |
| Device Info | 6EADC0DE-0004-4A21-9C5E-1B7F3D9E42A0 | Read | Device → App |
Wire format
One compact UTF-8 JSON object per message, newline-terminated; frames may split across BLE writes and notifications; official implementations cap a frame at 4096 bytes. Enum values are lowercase; duplicate fields are invalid; unknown types, missing fields, and out-of-range values fail closed — the message is discarded and the previous state is kept.
Device Info — capability declaration
On connect the App reads what the device can render and only sends what the device can consume. No indicator? Omit statusSlots and receive zero status traffic. One LED? Declare 1, not 8.
{
"protocol": "nexting-device",
"spec": "0.1.0-experimental",
"wire": [1],
"profiles": ["approval/1", "status/1", "navigation/1", "keys/1", "rotary/1", "voice/1", "text/1", "usage/1", "config/1"],
"model": "xiao-nrf52840-ref",
"fw": "0.1.0",
"max_message_bytes": 4096,
"max_summary_bytes": 240,
"statusSlots": 4
}6. Ground Rules
Deny by default. A device does nothing until the user explicitly authorizes it in the App; revocation stops all traffic.
Fail closed. Malformed, stale, oversized, or unauthorized input never approves anything and never corrupts the display.
Volatile state. Approvals and status live in RAM and clear on disconnect, reboot, or re-pairing.
Honest capabilities. Declare only what the hardware actually renders.
7. Source Code
Specification, shared test vectors, SDKs, reference firmware, and the full developer guides — quickstart, interface catalog, implementation tracks, conformance levels, use-case gallery: