Metadata-Version: 2.4
Name: aether-robotics
Version: 5.6.1
Summary: Autonomous multi-agent robotics system with DRL-First Hybrid FDIR
Author: Chahel Paatur
License: MIT
Project-URL: Homepage, https://aether-robotics.com
Project-URL: Documentation, https://aether-robotics.com
Project-URL: Issues, https://aether-robotics.com
Keywords: robotics,autonomous,FDIR,fault-detection,raspberry-pi,drone,AI
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: System :: Hardware
Classifier: Intended Audience :: Science/Research
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy<3.0,>=1.24.0
Requires-Dist: psutil<8.0,>=5.9.0
Requires-Dist: requests<3.0,>=2.32.0
Requires-Dist: pyyaml<7.0,>=6.0
Requires-Dist: posthog<7.0,>=3.0
Requires-Dist: rich<15.0,>=13.0
Requires-Dist: textual<7.0,>=0.50
Requires-Dist: prompt_toolkit<4.0,>=3.0
Provides-Extra: rag
Requires-Dist: sentence-transformers<6.0,>=2.7; extra == "rag"
Provides-Extra: viz
Requires-Dist: matplotlib<4.0,>=3.7.0; extra == "viz"
Provides-Extra: full
Requires-Dist: anthropic<1.0,>=0.50.0; extra == "full"
Requires-Dist: flask<4.0,>=3.0.0; extra == "full"
Requires-Dist: opencv-python-headless<5.0,>=4.8.0; extra == "full"
Requires-Dist: matplotlib<4.0,>=3.7.0; extra == "full"
Provides-Extra: pi
Requires-Dist: picamera2<1.0,>=0.3; extra == "pi"
Requires-Dist: RPi.GPIO<1.0,>=0.7; extra == "pi"
Requires-Dist: gpiozero<3.0,>=1.6; extra == "pi"
Requires-Dist: smbus2<1.0,>=0.4; extra == "pi"
Provides-Extra: drone
Requires-Dist: pymavlink<3.0,>=2.4; extra == "drone"
Requires-Dist: pyserial<4.0,>=3.5; extra == "drone"
Provides-Extra: ml
Requires-Dist: ultralytics<10.0,>=8.0; extra == "ml"
Requires-Dist: scipy<2.0,>=1.10; extra == "ml"
Requires-Dist: scikit-learn<2.0,>=1.3; extra == "ml"
Provides-Extra: telemetry
Provides-Extra: dev
Requires-Dist: pytest<10.0,>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio<2.0,>=0.23; extra == "dev"
Requires-Dist: anthropic<1.0,>=0.50.0; extra == "dev"
Dynamic: license-file

```
    ████████████████████████████████
   ████████████████████████████████
  █████╔═══════════════════╗█████
  █████║  ▄████▄   ▄████▄  ║█████
  █████║ ████████████████ ║█████
  █████║ ██████████████ ║█████
  █████║ █▀▀████████▀▀█ ║█████
  █████║  ▀████▀   ▀████▀  ║█████
  █████║▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄║█████
  █████║  ━━━━━━━━━━━━━━━  ║█████
  █████║  ▌   AETHER   ▐  ║█████
  █████║  ━━━━━━━━━━━━━━━  ║█████
  █████╚═══════════════════╝█████
   ████████████████████████████████
    ████████████████████████████████
```

# AETHER — Talk to your robot in plain English

> Install it on a Raspberry Pi robot, say what you want, and it works out which motors and sensors to use.

<!-- LAUNCH TODO: drop demo.gif into docs/ and replace this comment with:
     ![AETHER driving at a wall and stopping](docs/demo.gif)
     Suggested clip: `drive forward until you get close to an obstacle` —
     robot approaches a wall, stops short, terminal shows the plan alongside. -->

## Quickstart

```bash
pipx install aether-robotics
export GEMINI_API_KEY=...        # free key: aistudio.google.com/apikey
aether --mode agent
```

Then type what you want:

```
objective> drive forward until you get close to an obstacle
```

> On Raspberry Pi OS Bookworm and later, plain `pip install` is refused (PEP 668) — that is what `pipx` is for. If you would rather not use pipx: `pip install --break-system-packages aether-robotics`. Run `aether doctor` any time something looks wrong; it checks the install, the PATH, and whether your LLM provider actually answers.

## What hardware do I need

A Raspberry Pi and a robot chassis. Approximate 2026 street prices — check current listings, they move:

| Part | Options | Approx. |
|---|---|---|
| Raspberry Pi | Zero 2 W (cheapest that works), or Pi 4 / Pi 5 for vision | $15 – $80 |
| microSD card | 16 GB+ | $8 |
| Robot kit | SunFounder PiCar-X, GoPiGo3, Yahboom, Freenove, Adeept, Waveshare | $40 – $100 |
| Power | USB battery pack or 18650 cells (usually kit-included) | $0 – $20 |

A Pi Zero 2 W plus a budget chassis lands near **$50**; a PiCar-X with a Pi 4 is closer to **$150**. AETHER ships tested adapters for the kits above and will attempt to generate one for hardware it does not recognise.

No LLM bill: Gemini, Groq and OpenRouter free tiers comfortably cover a robot's planning rate, and Ollama runs fully offline.

## What it actually does

At startup AETHER scans I2C, GPIO and serial, works out what is physically attached, and either loads a tested adapter, generates one, or walks you through a calibration wizard. It builds a map of every motor and servo by pin, then an LLM turns plain-English objectives into tool calls against that map. Motion runs behind a layered safety chain — every path that starts a motor is required to stop it — and a fault-detection network watches the sensor stream while it runs.

## Security posture

**Shell and filesystem access are off by default.** AETHER plans with an LLM and
then executes what it planned, so the tools that could do real damage are not
registered unless you ask for them:

```bash
aether --mode agent                    # safe subset: sensors, motion, vision
aether --mode agent --allow-fs         # + file read/write, jailed to ~/.aether/workspace
aether --mode agent --allow-shell      # + allowlisted commands, no shell interpretation
```

Requests to loopback, LAN and cloud-metadata addresses are blocked (`--allow-private-network` to permit them), `--mode server` binds `127.0.0.1` and refuses to start without `AETHER_SERVER_KEY`, and generated adapter code is validated against a whitelist, dry-run in a resource-limited subprocess, and re-validated every time it loads.

Full details, threat model and residual risks: **[SECURITY.md](SECURITY.md)**. Report vulnerabilities to chahelpaatur@aether-robotics.com.

## Honest limitations

Read this before you decide it is broken:

- **Not a ROS replacement.** Single process, single machine. No distributed node graph, no real-time guarantees. It is a layer for one small robot, not a fleet or an industrial arm.
- **The mapper is not SLAM.** Frame-difference optical flow on a 100×100 grid, no loop closure, unbounded drift. Useful as short-horizon obstacle memory; do not navigate a building with it.
- **Monocular distance is a rough fit,** calibrated against one reference object size. Good enough for "stop before the wall", not for measurement.
- **Parallel execution helps less than it sounds.** The safety rules serialise motion — which dominates wall-clock — so most of the gain is on sensor-plus-compute groups.
- **Generated adapters need a physical test.** Static analysis and a sandboxed dry run cannot tell a correct pin mapping from a wrong one; only watching the robot move can. AETHER asks before doing that.
- **Flight is deliberately blocked.** AETHER speaks MAVLink as a companion computer and reads telemetry, but will not issue flight commands from an LLM plan.
- **LLM planners produce wrong plans sometimes.** There is a repair-and-fallback path, and failures are reported as DEGRADED rather than quietly succeeding — but supervise a robot that can move.

Full engineering detail, including the algorithms and where they came from: [docs/TECHNICAL_DESIGN.md](docs/TECHNICAL_DESIGN.md).

[Docs → aether-robotics.com](https://aether-robotics.com)

---

## What's New

### v5.2.4 — The 22-second embedding load, and the warm-up that never ran

Loading MiniLM takes **~22 s on a Pi 4**. Everything else was noise.

- **The async warm-up had been dead since v5.1.0.** Its only call chain was `warm_up_async ← auto_migrate_on_boot ← print_startup`, and v5.1.0 moved `print_startup()` inside `if _verbose:` — so no normal boot warmed the model. It is now started explicitly from boot, nice'd, and instrumented (`[EMBED] warm-up loaded in 21840ms`).
- **`available()` was loading the model.** It was `get_model() is not None`, so asking "are embeddings available?" performed the full load — on every `plan_cache.enabled()`, and in the guard in front of the warm-up itself. One level down, `is_installed()` did `import sentence_transformers`, which pulls torch. Both are now genuinely cheap (`find_spec`, 30 ms).
- **Exact-match cache tier, no model required.** Repeating an objective verbatim — the common case — is now a normalized string comparison. Embedding similarity remains only as the fallback for genuinely reworded objectives.
- **`AETHER_RAG_MODE=auto|always|off`** (default `auto`): while the model is cold, retrieval is *skipped* and planning proceeds normally. An optimisation that costs 22 s to save 1 s must yield, not block.
- **`get_model()` is now lock-serialised.** Without it the main thread never waited for the warm-up thread — it started a *second* concurrent 22 s load.
- **`--objective` one-shot was broken**: it used the autonomous path, which has no plan-cache lookup, and discarded the result. It now checks the cache and prints the outcome.
- Never stall silently: `[MEM] Loading embedding model (first use, ~20s on a Pi)...`

**A correction:** the v5.2.2 and v5.2.3 latency claims were measured on a macOS dev box and extrapolated. The import-time work in v5.2.3 was real but was never the 50 s. This one was found by measuring on the hardware that had the problem.

### v5.2.8 — The half-drawn box after every answer

Reported: every completed turn left a box with a top border and an input line but **no bottom** — "that sick half-finished" frame repeating down the scrollback.

Cause: prompt_toolkit's `bottom_toolbar` is **transient**. It is part of the live editing region and is *erased* when the prompt is submitted, so the closing border and status line — which v5.2.7 put in the toolbar — vanished, leaving an unclosed box permanently in the scrollback.

The closing border is now printed by AETHER after the prompt returns (in a `finally`, so an interrupt closes it too), giving a complete frame in history:

```
╭──────────────────────────────────────────────────────────╮
│ › whats the distance in front of you
╰──────────────────────────────────────────────────────────╯

  [CACHE] Reusing plan (similarity 1.00) — skipped the planning call
  [OK] get_distance → 7.8 cm (4ms)
```

The status line is deliberately **not** persisted — it is live chrome, and a stale copy above every answer would be noise.

### v5.2.7 — The input bar, rebuilt on prompt_toolkit

The bar had a structural flaw that no amount of padding could fix: with `print()` + `input()`, the **closing border and status line are printed only after `input()` returns**, so while you were typing they did not exist. The reported symptom — "I can't even scroll down to see most of it" — was literally true: there was nothing below to scroll to.

A box whose bottom edge sits below the input line is impossible that way. The bar now uses **prompt_toolkit**, whose `bottom_toolbar` renders below the input and stays visible during editing:

```
╭────────────────────────────────────────────────────────────╮
│ › drive forward until you get close to a wall
╰────────────────────────────────────────────────────────────╯

  ▸ rag on · cache 14 plans · anthropic · picarx
```

- The status line is now **live while you type** — `rag loading… → rag on` updates in place, which the print-based bar could never do.
- Status text is **AETHER orange** (`#FF7A00`) with a single `▸` marker.
- History, Ctrl-R search and emacs bindings come from prompt_toolkit, so the v5.1.1 "don't lose readline" requirement is satisfied by the library rather than worked around. History still persists to `~/.aether/objective_history`.
- Falls back to the previous print-based bar if prompt_toolkit is unavailable or the terminal misbehaves — **one strike, then fallback**, so a broken renderer can never make AETHER unusable. `AETHER_NO_PROMPT_TOOLKIT=1` forces the fallback.
- Non-TTY is unchanged: the exact `objective> ` prompt, no drawing (the byte-identical piped-output contract).

`prompt_toolkit` is a new core dependency — pure Python, ~3 MB, fine on a Pi.

### v5.2.6 — Input bar has room to breathe

The bar was a thin three-row strip pinned to the bottom of the terminal, with the status line squeezed against the edge or scrolled off entirely. Now:

```
╭────────────────────────────────────────────────────────────╮
│                                                            │
│ › drive forward until you get close to a wall
╰────────────────────────────────────────────────────────────╯

  ▸▸ rag on · cache 14 plans · anthropic · picarx

```

- **Padding inside the box** (`BOX_PAD_TOP`), so the input has vertical presence instead of being a slot.
- **A gap between the border and the status line**, so the line reads as its own band rather than being welded to the box.
- **Clear space below everything** — the reported symptom was that there was none.
- **The status line is accented, not dim grey.** It carries the one state worth watching (whether the embedding model is still loading); dim made it disappear into the log above it. Separators stay dim so the fields read first.

The whole assembly is 8 rows, so it still fits an 80×24 terminal. Non-TTY and `--no-color` are unchanged.

### v5.2.5 — Status line under the input bar

Session state now sits below the prompt as dim chrome, so the important one — whether the embedding model is still loading — is visible rather than being a silent 20-second wait:

```
╭───────────────────────────────────────────────────────────╮
│ › what's the distance in front of you
╰───────────────────────────────────────────────────────────╯
  ▸ rag loading… · cache 14 plans · anthropic · picarx
```

Slash commands change session state and reprint the line immediately:

| command | effect |
|---|---|
| `/rag on\|off\|auto` | retrieval mode at runtime |
| `/cache clear` | drop cached plans |
| `/provider <name>` | switch LLM provider mid-session |
| `/verbose` | toggle verbose output |

Under 80 columns fields drop right-to-left rather than wrapping — but **an active warning is ordered second, not last**, so it survives truncation. A status line that silently drops the one field reporting a problem would be the exact quiet-failure class the rest of this project has been removing. Non-TTY omits the line entirely (the byte-identical piped-output contract), `--no-color` keeps the text and drops the dim styling, and readline history is untouched.

One honest limitation: the line refreshes on each prompt cycle and on slash commands, not continuously while you are typing. Live in-place updates need ANSI cursor movement above the input row, which is what broke the box in v5.2.1 — so `rag loading… → rag on` appears at the next prompt rather than mid-keystroke.

### v5.2.2 — Boot is the latency now, so boot got measured

With planning cached at 0 ms and `get_distance` at 6 ms, the remaining ~5 s was boot — running before every objective, and entirely unmeasured. Instrumented first:

| phase | ms (macOS dev machine) |
|---|---|
| hardware_probe | 1485 |
| software_probe | 942 |
| camera_probe (nested) | 828 |
| i2c_scan | 0.1 |

`[BOOT] hardware_probe 1485ms · software_probe 942ms · … · total 2449ms` prints under `AETHER_VERBOSE=1`, and `boot_latency {phase, ms}` is emitted always — the point is the distribution across real installs, not just machines being debugged.

- **Cached hardware profile.** If the fingerprint is unchanged, the entire probe sweep is skipped: **3016 ms → 1 ms**. The fingerprint deliberately includes the I2C address scan (~340 ms on a Pi), not just device-node presence — swapping a PiCar-X HAT for a GoPiGo3 leaves `/dev/i2c-1` identical, and a fast boot onto the wrong adapter is not a win. Prints `[DISCOVERY] Using cached hardware profile (fingerprint match)`; invalidated by a hardware change or `aether doctor`.
- **One-shot mode:** `aether --mode agent --objective "what's the distance in front of you"` runs one objective and exits, skipping readline and the feedback prompt.
- **Input bar.** v5.2.1 printed a leading newline unconditionally, which put a blank row *inside* the panel after Enter — the reported symptom. It's now only printed when the cursor is genuinely mid-line (Ctrl-C, or teardown closing the box from a signal handler), plus a reserved row above and a blank line after the closing border so the panel renders fully on an 80×24.
- **Subcommands look like the rest of the program.** `doctor` renders section panels with a coloured `13 passing · 4 warnings · 0 failures` summary; bare `aether kits` lists the kits instead of dumping argparse usage (`--help` still does). Four "KEY not set" provider warnings collapse into one line — they were never four problems. Non-TTY output stays byte-identical.

### v5.2.1 — Plan cache tuning, measured against the pairs that must not match

A one-word difference (`"infront"` → `"in front"`) missed the cache and cost a fresh ~2.7 s planning call. Objectives are now **normalised before embedding** — lowercased, punctuation stripped, whitespace collapsed — so `"What's the distance?"` and `"whats the distance"` embed identically (measured 0.934 → 1.000).

**The threshold is now two thresholds, and the split came from measurement rather than judgement.** Lowering the cutoff to 0.88 across the board would have been unsafe. Measured with all-MiniLM-L6-v2 on pairs that must *never* match:

| pair | cosine |
|---|---|
| set servo to 30 / 130 degrees | **0.914** |
| open / close the gripper | **0.907** |
| drive until 5cm / 50cm from obstacle | **0.898** |
| drive forward for 2 / 20 seconds | **0.894** |
| turn left / turn right | **0.886** |
| drive forward / backward | 0.785 |

Five sit above 0.88. And the discriminator isn't "contains a number" — *open* vs *close the gripper* has no digit and no direction word and still scores 0.907. So the tier is chosen by **what the cached plan does**, not by how the objective was worded: a read-only sensor plan uses **0.88** (worst case, you re-read a sensor), anything that actuates keeps **0.95** (worst case, the robot moves differently than asked). `AETHER_CACHE_THRESHOLD` pins both.

- **Near-misses are logged** — `[CACHE] Near-miss (similarity 0.91, threshold 0.88) — planning` — and `plan_cache_miss` now carries the similarity, so the value is tunable from real distributions instead of another round of guessing.
- **Dedupe on insert.** Four phrasings of one question created four rows; a near-identical entry with an *identical* tool chain and params now refreshes its timestamp instead. Similarity alone never merges entries — "2 seconds" and "20 seconds" can be 0.89 similar and must stay separate.
- **Input bar teardown.** Shutdown messages printed *inside* the box, because AETHER's SIGINT handler emits the whole teardown log from within the signal handler before `SystemExit` ever reaches the prompt's cleanup. The exit path now closes the panel first.

### v5.2.0 — Latency reduction, and a security audit that changed the defaults

**Faster.** A repeat objective no longer calls the LLM at all. On real hardware `get_distance` executes in 4-6 ms while planning takes ~1 s, so for sensor-only work the model was ~99% of wall clock — and users repeat themselves constantly.

> **Note on the `[rag]` extra:** the embedding model costs **~22 s to load once per process on a Pi 4**. AETHER warms it in the background and, by default (`AETHER_RAG_MODE=auto`), skips retrieval entirely while it is still cold — so a cold first objective plans normally rather than waiting. Exact repeats of a previous objective are served from cache without touching the model at all.

- **Semantic plan cache.** A successful plan is stored against the embedding of the objective that produced it; a semantically equivalent objective replays it. **~6 ms instead of ~1000 ms — roughly 158× on a repeat.** Every entry is keyed on the robot's genome hash and a mismatch is a *hard* miss: a cached plan is instructions for moving a physical machine and must never replay against different hardware. Parameters are replayed verbatim, never interpolated — 0.95 cosine still admits "2 seconds" vs "20 seconds". Needs the `[rag]` extra; degrades to normal planning without it.
- **Anthropic prompt caching** on the static system prefix (~71 tool descriptions), plus **latency instrumentation** on every planning call: `[PLAN] via anthropic in 847ms (cached prefix: 4200 tok)`.
- **Provider detection persisted** for 24 h against a credential-presence fingerprint, so boot stays off the detection path.

**Safer — and the defaults changed.** A full security audit before public launch found working exploits, and the fix was a capability boundary rather than a better blocklist:

- **Shell and filesystem tools are no longer registered by default.** `--allow-shell` / `--allow-fs`. The old shell filter blocked `; | ` $(` in front of `shell=True`, which meant `&&`, newlines, `>`, `&` and `${VAR}` all passed — and `echo $ANTHROPIC_API_KEY` returned the key. Now: `shell=False` with an argv list, an allowlist of binaries, and a credential-scrubbed subprocess environment.
- **File tools are jailed** to `~/.aether/workspace`. The previous code called `realpath()` and used the result, which *normalises* `../../../../etc/passwd` into `/etc/passwd` and opens it. It now resolves and then compares against the root.
- **SSRF blocked** — `call_api` and `web_search` validate scheme and resolved address, and re-check the whole redirect chain, so a 302 into `169.254.169.254` cannot reach cloud metadata.
- **Server mode fails closed**, binding `127.0.0.1` and refusing to start without `AETHER_SERVER_KEY`. It previously bound `0.0.0.0` and accepted every request when no key was set.
- **Generated-code validation inverted to a whitelist.** `getattr(__builtins__, "__import__")("os")`, `globals()[...]` and string-built names all walked past the old denylist. The dry-run moved from a thread in AETHER's own interpreter to a subprocess with `RLIMIT_CPU`/`AS`/`FSIZE`, and saved adapters are re-validated on every load.
- **Gemini, Groq and HuggingFace keys are now redacted** — v5.0.0 added those providers without touching `security.py`. URL query strings are stripped everywhere, since Gemini authenticates with `?key=`.
- **No more unattended `sudo`.** AETHER prints the command instead of running it.
- Dependency upper bounds on everything; `~/.aether/` is `700`.

See **[SECURITY.md](SECURITY.md)** for the capability model and threat model.

### v5.1.2 — Feedback routing, session counting, quieter boot

- **Support goes to email, not a private repo.** The GitHub repository is private, so every link to it — the issues page, the "file a PR" hints in `aether kits`, the PyPI sidebar, and the auto-updater's releases API — was a 404 for everyone who isn't the author. All of it now points at **chahelpaatur@aether-robotics.com**, from one constant. The updater checks **PyPI** instead of the private releases API (it had been silently finding nothing on every run), and a pip install is told the actual `pip install -U` command rather than "download the latest release from GitHub".
- **The feedback prompt moved back to the exit path** and no longer greets you at boot. v5.1.1 over-corrected: it removed the exit-path call entirely instead of keeping it as primary, so the question arrived before you'd run anything. It now fires on the way out for both `quit` and Ctrl-C — `SystemExit` is caught at the top of the REPL loop, because AETHER's SIGINT handler calls `sys.exit()` from inside the handler, and by then motors are already stopped. Prompting from within the signal handler would re-enter readline, which isn't reentrant. The in-session fallback survives only for sessions that died hard (SIGKILL, power loss), and fires after a *successful* objective.
- **Session counting starts when the feature shipped.** An upgrading install counted its entire history and announced "5 objectives across 37 sessions" on the first v5.1.1 boot. A baseline is pinned on first run and both numbers — sessions and objectives — are now measured over the same window.
- **Input bar: bright white border, and a blank line beneath it** so the bottom edge is visible instead of flush against the terminal. Readline history is unchanged.
- **The "Missing libraries detected" block shows once**, not on every boot. It had stopped blocking in v5.1.0 but kept printing.
- **Boot is quiet by default.** The ~20 lines of `[ADAPTER]`/`[TOOLS]`/`[NAV]`/`[GENOME]`/`[SKILLS]` progress above the summary panel are internal detail and are now verbose-only. Warnings and faults still print — silencing by default and opting problems back in is how a real failure gets hidden. Normal boot is: banner → summary panel → recent memory → input bar. `AETHER_VERBOSE=1` or `--verbose` restores everything.

### v5.1.1 — Boot output, an input bar, and four bugs from hardware testing

- **The feedback prompt actually fires now.** It shipped in v5.1.0 and never appeared once. AETHER installs a SIGINT handler that calls `sys.exit()` from inside the handler, so Ctrl-C unwinds as `SystemExit` rather than `KeyboardInterrupt`, sets a non-zero exit code, and skipped the `_exit_code == 0` gate the prompt sat behind. Since Ctrl-C is how most people leave a REPL, feedback would have been collected from almost nobody. It now runs at the **start** of a session — after boot, before the first objective — which is independent of how the previous session ended and never competes with the motor-stop teardown. `AETHER_FEEDBACK_DEBUG=1` forces it, so this is testable without three real sessions.
- **OLED false positives, third occurrence, different cause.** v4.5.2 fixed detection (probe with a real write) and the `setmode` error. This one is a *different* RPi.GPIO failure: `GPIO.cleanup()` elsewhere **releases** luma's DC/RST channels, and the next write fails with `The GPIO channel has not been set up as an OUTPUT` — a string the v4.5.2 guard did not match, so it went straight through as a hard error. Re-running `setmode` cannot fix a released channel; the device is now rebuilt once so luma reconfigures those pins. Separately, an OLED that fails its probe is **no longer registered at all** on a Pi, so the planner can't reach for `draw_face` when you say hello.
- **`Detected: generic hardware` on a correctly-identified robot.** Only the multi-adapter branch called `set_primary_adapter`, so the common single-robot case left it unset. Now reads `Detected: SunFounder PiCar-X — 71 tools across 1 adapter`, with vendor-correct product names for every shipped adapter.
- **The install prompt stopped returning.** v5.1.0 showed the one-liner on the first run and then fell back to the blocking `Install missing libraries? [Y/n/skip]:` on runs 2 and 3. Boot never prompts now; `aether kits` is the way in.
- **Boot is a summary, not an inventory.** ~70 lines of hardware/software/network/environment collapse to one panel that reports only what is broken or notable:

  ```
  ╭─ AETHER v5.1.1 ─────────────────────────────────────╮
  │  SunFounder PiCar-X · 71 tools · Capability 79/100   │
  │  Provider: gemini (free tier)                        │
  │  Camera unavailable · OLED unavailable               │
  ╰──────────────────────────────────────────────────────╯
  ```

  The full dump is one `capabilities` command away, `aether doctor` is unchanged, and `AETHER_VERBOSE=1` (or `--verbose`) restores everything.
- **A bordered input bar — and arrow-key history that never existed.** The prompt is now a box. Verifying the "don't lose readline" requirement turned up that AETHER never imported `readline` at all, so up-arrow emitted a literal `^[[A` and there was no history to lose; it's now enabled with history persisted to `~/.aether/objective_history` across sessions. The box is drawn with no ANSI cursor movement (an earlier draft used `\x1b[2A` and broke as soon as typed input wrapped), and off-TTY it is the exact `objective> ` prompt with no drawing at all.
- **Quieter execution blocks.** `[CORRECT] Skipped verification` was internal detail printed on every clean step and is now verbose-only; known result shapes read as answers (`{'distance_cm': 8.3}` → `8.3 cm`); a clean run collapses to `✓ Complete · 1 action · 0 faults`. DEGRADED and FAILED keep the full block — compressing those is how a problem gets scrolled past.

### v5.1.0 — Onboarding polish, and one question

A first boot printed ~25 lines of ALSA/JACK errors, a 71-line tool dump, and an install prompt about `torch` before the user had run anything. Nothing was broken; it read as though everything was.

- **Third-party stderr noise is gone.** ALSA/JACK spew from `robot_hat`'s pyaudio import and OpenCV's V4L2 warnings are suppressed at the file-descriptor level during vendor imports only — `contextlib.redirect_stderr` cannot catch these, since they come from C libraries writing to fd 2 underneath the interpreter. Everything AETHER itself prints stays visible, including errors. `AETHER_VERBOSE=1` restores the lot.
- **`pipx` is now the documented install path** for PEP 668 environments (Raspberry Pi OS Bookworm+), with `--break-system-packages` as the fallback. `aether doctor` detects an externally-managed Python and prints the command that will actually work.
- **`aether doctor` catches the installed-but-not-on-PATH case** — the state where `import aether` works, the `aether` command does not, and it looks like a broken install. It prints the exact export line for *your* shell.
- **The boot screen is for a first-time user now.** One `Detected: <adapter> (N tools)` line instead of the tool dump, plus a short quickstart with three objectives that robot can actually attempt (gated on the tools present, so a camera-only robot is never told to drive). The full listing moved to the `tools` command and `--verbose`; `help` lists the rest.
- **The optional-accelerator prompt became a single line.** `torch`/`dronekit`/`tflite`/`ultralytics` are no longer offered before you have run an objective and could judge whether you need them.
- **The updater reports the real version.** `CURRENT_VERSION` had been pinned at `"3.0"` for twelve releases; it now reads the package version, which is also the second line a new user sees.
- **One feedback question, once per install.** After the third session with at least one successful objective: four keys, no survey. If you declined telemetry you are never asked at all, non-interactive runs never prompt (and never burn the one-time opportunity), and free text is redacted before storage — and never transmitted, since only `has_comment` is on the telemetry allowlist.

### v5.0.2 — A shut-down Gemini model, reported as available

Google retired `gemini-2.0-flash` on 2026-06-01. v5.0.0/v5.0.1 pinned that ID, so the free tier — the entire point of v5.0.0 — stopped working for every install at once, while `aether doctor` still printed a green check.

- **Model IDs are no longer pinned in place.** Every provider takes `AETHER_<NAME>_MODEL`, so you can move to a working model without waiting for a release:

  ```bash
  export AETHER_GEMINI_MODEL=gemini-3.5-flash   # also ANTHROPIC / GROQ / OPENROUTER / OLLAMA
  ```

- **AETHER self-heals through a retirement.** A 404 triggers a `ListModels` lookup, selects the best live Flash model, retries once and logs it: `[gemini] gemini-2.0-flash retired — switched to gemini-3.5-flash`. Remembered for 24 h so the next run does not repeat the wasted call.
- **`aether doctor` probes instead of checking for a key**, because key-presence is not availability. Three states, cached 15 minutes:

  ```
  ✓ gemini    verified (free tier, multimodal) — model: gemini-3.5-flash
  ⚠ gemini    GEMINI_API_KEY set but call failed: HTTP 404: ...no longer available
  ⚠ gemini    GEMINI_API_KEY not set (free key: aistudio.google.com/apikey)
  ```

  `AETHER_DOCTOR_NO_PROBE=1` for offline use. Every configured provider failing is a FAIL, not a row of warnings.
- **A provider outage now reports DEGRADED.** Previously the 404 fell through to the keyword planner and the objective still reported SUCCESS with 0 faults.
- **Opt-in live smoke test.** Every provider test before this release mocked HTTP — which is exactly what hid the outage, since a dead model ID cannot fail a mocked test. `AETHER_LIVE_PROVIDER_TESTS=1 pytest tests/test_live_providers.py` makes one real call per configured provider and asserts the *shipped* default still answers.

The default is `gemini-3.5-flash`, deliberately not `gemini-2.5-flash` — that one already carries a 2026-10-16 shutdown date and would have reproduced this bug within two months.

### v5.0.1 — Plans survive malformed JSON, and skills stop overstating success

- **A trailing comma no longer throws away a correct plan.** Models occasionally emit `[{...},]`, which strict `json.loads` rejects — so a plan naming the right tool with the right parameters was discarded and the keyword fallback ran instead. Parsing is now tolerant: strict first, then `json5` if installed, then a **string-aware** repair that tracks string literals and escapes, so commas and braces *inside* values are never touched. Repairs are logged (`Repaired malformed JSON (trailing comma) — plan recovered`) so the rate is measurable.
- **Fallback keeps the parameters the model chose.** Previously a parse failure meant `drive_until_obstacle(target_cm=5)` became `drive_until_obstacle()` on defaults; the parameters are now recovered from the raw response.
- **A skill that aborts no longer reports success.** A drive that halted on a sensor fault returned `SUCCESS` with 0 faults. Outcomes are now explicit: reaching an obstacle or running the full time budget is success; a sensor fault, a cancellation or a missing tool is a failure with a stated reason. `watch_distance` that collects zero readings is likewise a failure — observing nothing is not a successful observation.
- **The systemic root, fixed once.** A tool returning a dict *without* a `success` key was reported successful unconditionally, even when it carried an `error`. That is the same shape as three earlier bugs; an explicit `error` now means failure regardless of the missing key.
- **Skill results are readable.** Parallel-skill output was truncated mid-payload, hiding the only fields that matter:

```
before: [OK] drive_until_obstacle → {'name': 'drive_until_obstacle', 'tasks': [{'name': 'motor', 'result': None, ...
after:  [OK] drive_until_obstacle → stopped_reason=obstacle at 21.2 cm, final=21.2cm, elapsed=4.4s
        [OK] watch_distance → 15 samples, min=21.4cm, max=98.2cm, avg=54.1cm
```

### v5.0.0 — Bring your own LLM (AETHER is now free to run)

AETHER no longer requires a paid account. The planning LLM is pluggable, and the free tiers are more than enough for a robot: planning runs at roughly 2–5 requests per minute, well inside what Gemini and Groq give away.

```bash
pip install aether-robotics
export GEMINI_API_KEY=...     # free at aistudio.google.com/apikey
aether --mode agent
```

| Provider | Cost | Speed | Vision | Notes |
|---|---|---|---|---|
| **Gemini** | free tier | fast | yes | recommended starting point; native JSON mode |
| **Groq** | free tier | fastest | no | LPU inference, very low latency |
| **OpenRouter** | free tier | varies | yes | aggregator; `:free` model by default |
| **Ollama** | free (local) | hardware-dependent | no | no key, no network — everything stays on the robot |
| **Anthropic** | paid | fast | yes | best planning quality; unchanged from v4.5.3 |
| **AETHER Cloud** | — | — | — | managed tier, not yet available |

- **Auto-detection with fallback.** Providers are probed in order and the boot line names what is active: `[LLM] Provider: gemini (free tier) · fallback: groq, ollama`. Free tiers are unstable by nature, so a 429, 5xx, timeout or connection error moves to the next provider and logs the switch.
- **Never falls back on a bad response.** Only transient transport failures trigger a switch. A response AETHER cannot parse is reported (with the raw text, tagged by provider) rather than silently re-asked elsewhere — asking a second model the same question hides bugs and spends another quota to get the same answer.
- **Pin one provider** with `AETHER_PROVIDER=gemini`, which disables fallback entirely.
- **`aether doctor`** gains an LLM Providers section listing every provider, why each is unavailable, and which is active. A missing `ANTHROPIC_API_KEY` is no longer a failure — a free tier alone is a valid setup.
- **Nothing configured?** You get the free options first, with signup links and the exact `export` line — and exit 1, not a traceback.

Existing Anthropic users are unaffected: with `ANTHROPIC_API_KEY` set it sits first in the chain and planning behaves exactly as it did in v4.5.3.

### v5.0.2 — Model IDs move; a key is not a working provider

Google shut down `gemini-2.0-flash` on 2026-06-01. v5.0.0/v5.0.1 pinned that ID, so the free tier — the entire point of v5.0.0 — stopped working for every install at once, while `aether doctor` still printed a green check.

- **Model IDs are no longer pinned in place.** Every provider accepts `AETHER_<NAME>_MODEL`, so you can move to a working model immediately without waiting for a release:

  ```bash
  export AETHER_GEMINI_MODEL=gemini-3.5-flash    # also: ANTHROPIC / GROQ / OPENROUTER / OLLAMA
  ```

- **AETHER self-heals through a retirement.** A 404 triggers a `ListModels` lookup, picks the best live Flash model, retries once and says so: `[gemini] gemini-2.0-flash retired — switched to gemini-3.5-flash`. The choice is remembered for 24 h so the next run does not repeat the wasted call.
- **`aether doctor` now probes instead of checking for a key**, because key-presence is not availability. Three states, cached for 15 minutes:

  ```
  ✓ gemini    verified (free tier, multimodal) — model: gemini-3.5-flash
  ⚠ gemini    GEMINI_API_KEY set but call failed: HTTP 404: ...no longer available
  ⚠ gemini    GEMINI_API_KEY not set (free key: aistudio.google.com/apikey)
  ```

  Use `AETHER_DOCTOR_NO_PROBE=1` offline. If every configured provider fails its probe, that is now a FAIL, not a row of warnings.
- **A provider outage is reported as DEGRADED.** Previously the 404 fell through to the keyword planner and the objective still reported SUCCESS with 0 faults — it happened to pick a workable tool from memory. Planning silently losing its LLM is not a clean run.
- **Opt-in live smoke test.** Every provider test before this release mocked HTTP, which is exactly what hid the outage: a dead model ID cannot fail a mocked test. `AETHER_LIVE_PROVIDER_TESTS=1 pytest tests/test_live_providers.py` makes one real minimal call per configured provider, and asserts the *shipped* default model still answers. Run it before tagging.

Note on defaults: the current default is `gemini-3.5-flash`, deliberately **not** `gemini-2.5-flash` — that model already carries a 2026-10-16 shutdown date and would have reproduced this bug about two months after release.

### v4.5.3 — Hotfix: retrieval no longer breaks planning, and AETHER won't fake a motion command

- **Retrieved memory can't hijack the output format.** v4.3.0 appended the RAG context *after* the planner's "reply with valid JSON only" rule, so the last thing the model read was arrow-formatted prose. Once memory was migrated, every plan failed to parse. Context now goes **before** a restated JSON contract, and the block itself uses plain prose — no arrows, no bracketed lists.
- **Parse failures show the response.** A bare "Could not parse JSON" made this class of bug undiagnosable. The raw reply (first 600 chars) is now printed on any parse failure, in both the planner and the decomposer.
- **A movement command is never answered by a web search.** `web_search` was the universal last-resort fallback in three separate places, so "drive forward until you get close to the wall" became a *web search about driving* — reported as SUCCESS with 0 faults, then replayed forever from memory. Now: research fallbacks are vetoed for motion intent, cached chains whose category contradicts the objective are discarded, and an intent/execution mismatch reports `MISMATCH` instead of SUCCESS. If no motion tool can be selected, AETHER says so and names the motion tools that do exist.
- **Quieter diagnostics.** huggingface_hub's unauthenticated-request warnings no longer print mid-planner-call.

### v4.5.2 — Hotfix: reactive tools reachable in autonomous mode

- **Autonomous mode registers parallel skills.** Agent mode merged registered skill names into the tool manifest; the autonomous bootstrap discarded them. `drive_until_obstacle` was in the registry but absent from the manifest, so the planner's pick was rejected as "unavailable" and the goal died with "try rephrasing". Both modes now share the same merge.
- **The planner only advertises tools that exist.** v4.5.1's routing guidance names `drive_until_obstacle` and `watch_distance` explicitly, so it is now withheld on robots where they aren't registered — advertising an unresolvable tool is a manifest bug, not an LLM mistake.
- **Registry gaps are reported honestly.** A dropped tool triggers one re-plan with that tool barred; if the goal still can't be met, AETHER names the missing tool and the capability it needs instead of blaming your wording.
- **OLED detection verifies instead of guessing.** Discovery used to report an OLED as `[OK]` whenever the SPI node existed and `luma.oled` imported — even with no panel wired. Every write then failed on GPIO pin-numbering mode, the planner kept adding display steps because the tool looked available, and objectives whose real work had succeeded were reported DEGRADED. Detection now performs a real write and reports the reason when it fails; writes restore BCM mode and retry once (a later `GPIO.cleanup()` clears it); the planner no longer adds display steps unless you ask for one; correction aborts immediately on errors no retry can fix; and an unusable *output sink* no longer counts as a task fault.
- **No command exits 0 in silence.** The TUI restores the terminal on exit, which made a quick open/close look identical to a command that did nothing; it now prints a closing line, falls back to the classic CLI on *any* startup error, and `main()` returns an explicit exit code. New subprocess smoke tests invoke the real entry point and assert non-empty output for every documented invocation — the class of check that in-process tests cannot make.

### v4.5.1 — Reactive and observational sensing (safety fix)

v4.5.0 could run a motion and a sensor read concurrently, but `get_distance` is **single-shot**: paired with a 3-second drive it returned one reading at the start and the robot then travelled blind. Sensing *during* motion needs primitives that keep sensing.

- **`drive_until_obstacle`** — reactive. Drives forward and stops itself when the ultrasonic sees an obstacle, easing off speed on approach. Use it whenever a goal says "watch for obstacles", "don't crash", or "stop when". Forward only: the sensor faces forward, so reversing "until obstacle" is refused rather than run blind.
- **`watch_distance`** — observational. Samples the sensor repeatedly for a whole window and returns every reading plus min/max/avg, without touching the motors — so it is safe in a parallel group with one motion tool. Optional `stop_on_threshold_cm` acts as a tripwire.
- **Intent routing** — the planner now distinguishes three intents that look alike in English: *reactive* ("watching for obstacles" → `drive_until_obstacle`), *observational* ("log the distances" → motion + `watch_distance`), and *independent* ("at the same time" → a normal parallel group). When a goal is ambiguous and motion is involved, it prefers reactive — an unnecessary stop is cheap, a collision is not.
- **Device contention** — the safety validator now rejects two tools claiming the same physical device even when their names differ (`watch_distance` + `get_distance`, or either alongside `drive_until_obstacle`, which polls that sensor internally).

```
goal> drive forward for 3 seconds while watching for obstacles
  [EXEC] drive_until_obstacle(max_duration_s=3.0, stop_distance_cm=20)
  [OK] obstacle at 21.2 cm — stopped
```

### v4.5.0 — Parallel autonomous execution (with hard safety guards)

Autonomous mode now runs physically-independent sub-objectives **concurrently** instead of one at a time — so the robot can observe *while* it moves, not only after it stops.

- **Parallel groups** — sub-objectives may declare `"parallel_group": "name"` to run together and `"depends_on": ["group"]` to wait. Omit both and they run sequentially, exactly as in v4.0.0.2. Existing goal files keep working untouched.
- **Motion + sensing** — running a motion and an observation together. ⚠️ Pair the motion with `watch_distance` (v4.5.1), not a bare `get_distance`: `get_distance` is single-shot and returns one reading at the start of the group, so the robot travels blind for the rest of the motion.
- **Hard safety validator** — grouping is enforced, not trusted. Five rules reject unsafe concurrency and auto-serialize it:

  | Rejected | Why |
  |---|---|
  | Two locomotion tools | they fight over the same motors |
  | `emergency_stop` + anything | an emergency stop must run alone |
  | Two writes to one servo channel | final position undefined |
  | Two actuator writes on one adapter | the adapter can't service both |
  | The same sensor twice | one device, and both reads sample the same pose |

  A rejected group still executes — one task at a time — and the reason is fed back to the planner so it stops proposing that shape.

- **Safety chain unchanged** — the v3.9.0 executor still owns daemon workers, the process-wide stop, and the atexit halt. Ctrl-C mid-group pauses the goal (resumable), fires emergency stop, and leaves nothing latched. `emergency_stop` is always forced to run alone.
- **Analytics** — `parallel_group_executed`, `parallel_group_rejected`, and `parallelism_speedup` (sequential estimate vs actual wall time) show where concurrency is paying off.

```
[AUTONOMOUS Group 1/3] Running 2 tasks in parallel
  ● get_distance()
  ● capture_image()
  [OK] 2/2 completed in 411ms
```

### v4.3.0 — Memory retrieval (RAG over your own history)

AETHER now learns from what *you* have already asked it to do. Every recorded objective is embedded locally, and each new plan is grounded in the most similar successful runs from your history.

Retrieval is **opt-in** (v4.3.0.1): `pip install 'aether-robotics[rag]'`. The extra pulls PyTorch, which is a large download and has no wheels on 32-bit Raspberry Pi OS — so the core install stays small and AETHER runs identically without it, just with retrieval switched off.

- **Local embeddings** — `all-MiniLM-L6-v2` (22 MB, 384-dim, ~50 ms on a Pi 4). Downloads once, then runs entirely offline; no objective text ever leaves the machine.
- **Semantic retrieval** — `retrieve_similar(query, k, min_similarity, filter_success)` finds past objectives by meaning, not keywords: "go forward for three seconds" matches a stored "drive forward for 2 seconds" at 0.71 similarity.
- **Grounded planning** — the agent planner, the autonomous decomposer, and `aether --fix` each get a `RELEVANT PAST OBJECTIVES` block of successful precedents (~70 tokens). With no relevant history the block is omitted entirely, so prompts are byte-identical to v4.2.0.
- **`aether memory`** — inspect and search what AETHER remembers:

```bash
aether memory                       # counts, embedding state, disk usage
aether memory list -n 10            # recent objectives and their tool chains
aether memory search "turn around"  # semantic search with similarity scores
aether memory migrate               # (re-)embed entries
aether memory export dump.json      # dump to a file
```

- **Automatic migration** — entries recorded before v4.3.0 are embedded on first boot with progress output, written atomically; an interrupted migration simply resumes next run.
- **Privacy** — objectives are scrubbed by the v3.9.6 redactor *before* embedding, so no vector is ever derived from a secret. The `memory_retrieval` telemetry event carries only a query hash, result count, top similarity, memory size, and latency — never the query text and never a vector.

Retrieval degrades gracefully: without the `[rag]` extra (or if the model can't load) the retrieval block is omitted and planning behaves exactly as it did in v4.2.0 — no errors, no stack traces. `aether memory` tells you which state you're in.

### v4.2.0 — Full-screen TUI

Run `aether` with no arguments and the terminal becomes a product-grade, Claude Code-style TUI built on [textual](https://github.com/Textualize/textual) — minimal, orange-on-your-terminal-background, no full-screen repaint:

- **Layout** — header (version · mode · adapters), a sidebar mode list with the AETHER mascot parked bottom-left, the active mode's pane, and a live status bar (adapters · telemetry · session clock · action count · animated busy spinner).
- **Agent mode** — type an objective; planning renders in-flow right below it as a live block: a thinking animation while Claude plans, then a concise "thought" chain (`move_forward → get_distance → stop`) and a step list whose dots animate ○ pending → spinner running → ● done, each with its wall-clock seconds.
- **Doctor mode** — the diagnostic report as live ✓/⚠/✗ sections with a rerun key.
- **Slash commands + palette** — `/agent`, `/doctor`, `/clear`, `/help`, `/exit`, … from the input, or `Ctrl+P` for a fuzzy command palette. `Ctrl+D` exits, `Ctrl+C` cancels the current operation (never exits), `Ctrl+L` clears.
- **Classic CLI untouched** — every `--mode`, `doctor`, `analytics`, `--fix`, `--calibrate` invocation runs the exact pre-4.2.0 CLI. The TUI opens only for bare `aether` (or `aether --tui`) on an interactive terminal; pipes/CI and `AETHER_CLASSIC=1` always get the classic REPL.

```bash
aether                    # full-screen TUI
aether --tui              # explicit
AETHER_CLASSIC=1 aether   # force the classic REPL
```

Autonomous, Calibrate, Fix, and Analytics currently open in the TUI as panes pointing at their classic commands; native TUI widgets for them land in a follow-up.

### v4.1.5 — Rich terminal UI

The whole CLI got a Claude Code-inspired visual overhaul — same flow, same commands, just rendered with [rich](https://github.com/Textualize/rich):

- **Boot log adapter panels** — each loaded adapter shown with its role (● primary / ○ namespaced), tool count, I2C address, and claimed GPIO pins.
- **Autonomous mode live status bar** — a persistent panel showing the goal, a progress bar, the current sub-objective, and elapsed time vs. budget, updated between steps with ✓/▶ step markers.
- **`aether analytics` dashboard** — sessions overview, top-tools and adapters tables, `--fix` and autonomous panels, color-coded success rates.
- **`--calibrate` wizard** — numbered menu tables and validated rich prompts.
- **LLM spinners** — a live spinner whenever AETHER waits on Claude (planning, goal decomposition, `--fix` proposals).
- **Colored status tags** — `[OK]`/`[FAIL]`/`[WARN]` colorized in terminals.
- **Strict plain-text fallback** — in pipes, CI, or with `--no-color` / `AETHER_NO_COLOR=1`, every output path emits the exact pre-4.1.5 plain text, so log parsers and scripts are unaffected.

### v4.1.0 — Local analytics + opt-in telemetry

- **`aether analytics`** — local usage dashboards: sessions, top tools by call count and success rate, adapters loaded, error patterns, `--fix` and autonomous-mode stats. Data lives in `~/.aether/analytics/`; `--export FILE` dumps JSON, `--clear` wipes it.
- **Telemetry is strictly opt-in** (v4.1.0.2: one-time consent prompt on every mode entrypoint). When enabled, anonymized events go to PostHog — goal text and errors are hashed, never sent raw. Toggle with `aether analytics --enable-telemetry` / `--disable-telemetry`; inspect with `--telemetry-status`.

### v4.0.0 — Autonomous mode

Give AETHER a high-level goal and it decomposes, executes, observes, and replans on its own:

```
aether --mode autonomous --goal "map out the room" --time-budget 300
```

- **LLM goal decomposition** — the goal becomes an ordered list of sub-objectives bound to your robot's actual registered tools (never legacy/unavailable ones, v4.0.0.2).
- **Self-directed execution loop** — each step executes against the live ToolRegistry, logs an observation, and persists the goal after every state change; Ctrl-C loses at most the in-flight step.
- **Adaptive replanning** — a failed step triggers an LLM replan of the remaining plan, or a clean abort with a reason.
- **Resumable** — `--resume` continues a paused/timed-out goal, `--status` inspects it, `--history` lists finished goals, `--clear` drops it.
- **Safety** — motors are stopped only at end-of-goal or interrupt (each tool already carries its own try/finally stop); a final report synthesizes what actually happened from real observations.

### v3.11.5 → v3.11.10 — Namespacing, servo naming, GPIO-conflict detection

- **Tool namespacing** (v3.11.5) — every adapter's tools register as `<adapter>.<tool>`; bare names route to the **primary adapter**, secondaries stay reachable via their namespace. First-loaded-wins collisions are gone.
- **`--calibrate` structured wizard + named servos** (v3.11.6) — map channels, name servos (`gripper` → first-class `move_gripper(angle)` tool at next boot), set pulse-width limits, and test channels per adapter.
- **GPIO conflict detection** (v3.11.10) — adapters declare their BCM pins; conflicts are detected at load and shown in `aether doctor`'s GPIO allocation map.
- **PCA9685 safety hardening** — guaranteed hard-off via MODE1 SLEEP with wake-on-write, FULL-OFF stop writes (no auto-spin on restart), neutral pulses for continuous servos, and exit hooks that fire the correct namespaced `emergency_stop`.

### v3.11.0 — Multi-HAT discovery + multi-adapter loading

AETHER no longer assumes exactly one HAT. It now scans **all** hardware and loads **every** detected adapter.

- **Multi-pass discovery** — every I2C device (and USB-serial port, for MAVLink) is probed; discovery returns a confidence-ranked list instead of stopping at the first match:
  ```
  [DISCOVERY] Detected hardware:
    - I2C 0x14: sunfounder_picarx (confidence: 0.95)
    - I2C 0x16: yahboom_robot_hat (confidence: 0.90)
  ```
- **Multi-adapter registration** — all detected adapters register in confidence order. Tool-name collisions are **first-loaded-wins**; the ignored tool is preserved in a collision shadow registry (a `_TOOL_ORIGIN` map attributes every tool to its owning adapter) and a warning is printed.
- **robot.json v1.1** — composite robots get an `adapters[]` array attributing tools + I2C address to each adapter. v1.0 (single-adapter) genomes keep loading unchanged.
- **`aether doctor`** shows each detected adapter, per-adapter tool counts, and a predicted collision count.
- **PCA9685 all-call filter** — the 0x70 broadcast address is dropped from discovery when 0x40 is also present (it's the same chip answering twice).

Tool collisions were first-loaded-wins in this release; namespacing (v3.11.5) and GPIO-conflict detection (v3.11.10) superseded that — see above.

### v3.10 — Self-Healing (`aether doctor`, `aether kits`, `aether --fix`)

AETHER can now diagnose and repair itself.

- **`aether doctor`** (v3.10.0) — a read-only diagnostic: Python/OS, group membership, API key, I2C/serial/camera/SPI hardware, which adapter would load, file-system state, and recent boot errors. Exits non-zero if anything fails.
- **`aether kits`** (v3.10.0) — `list` / `info <kit>` / `install <kit>`. `info` is read-only (queried without touching hardware); `install` runs an auditable per-kit YAML recipe with explicit consent at each `sudo` step.
- **`aether --fix`** (v3.10.5 → v3.10.10) — describe a misbehavior in plain English and AETHER proposes a fix, then **drives the robot to verify it** before keeping it:
  - **Value calibration** via `~/.aether/overrides.json` (adapter-qualified keys, full history, atomic writes) — e.g. "the car doesn't turn enough" → bumps `sunfounder_picarx.turn_angle`.
  - **Code patches** via `~/.aether/proposed_patches/` (v3.10.10) — a unified diff against an `aether/` file, restricted by an allowlist (no vendor/test/version files), `patch --dry-run`-gated, backed up before apply, verified by a behavior test in a fresh subprocess, and auto-rolled-back on failure.
  - Patches never auto-apply; every behavior test is motor-safety gated; the boot path warns if a pip upgrade overwrote an active patch.

See [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md) (full reference).

### v3.9.x — Motion quality, safety, and three more kits

- **10 Tier 1 adapters** (v3.9.7): added SunFounder **PiDog** (quadruped walking gait), **Yahboom** robot HAT (`YB_Pcb_Car`, I2C 0x16), and **GoPiGo3** (Dexter Industries) — bringing built-in kit support to ten.
- **Smooth driving** (v3.9.3): `drive_until_obstacle` drives continuously (re-issuing only on approach-speed band changes) to eliminate start-stop jitter; pulsed fallback for non-SunFounder adapters.
- **Robust ultrasonic** (v3.9.4–v3.9.5): boot-time sensor health probe + clearer wiring guidance, and a **consecutive** (not cumulative) bad-read counter so motor-EMI noise no longer trips false "sensor failing" halts.
- **Security** (v3.9.6): API keys and other secrets are redacted from experience memory (`redact_secrets`), with a one-time scrub of any previously-leaked secrets at startup. Also restored OLED GPIO mode after the motor probe.

### v3.7.0 — Auto-Adapter System

AETHER now detects unknown robot HATs and boards automatically during `--calibrate`. Seven pre-built Tier 1 adapters cover the most common kits (SunFounder PiCar-X, Freenove 4WD and Mecanum, Adeept HAT v3, PCA9685 generic, L298N direct GPIO, Waveshare motor driver). For anything else, Tier 2 reads the manufacturer's Python library, calls Claude to generate an adapter, runs it through 6 static-validation rules, and requires a live hardware behavioral test before accepting it. Tier 3 is a guided interactive wizard for when no library is available. All generated adapters are stored locally at `~/.aether/adapters/` and load automatically on future runs.

New flags: `--generate-adapter-from <path>`, `--save-to <path>`, `--no-auto-adapter`.

See [docs/auto-adapter-system.md](docs/auto-adapter-system.md).

### v3.6.0 — MAVLink Quadcopter Integration

AETHER now drives MAVLink flight controllers (INAV / ArduPilot / PX4) through the same natural-language interface as GPIO robots. The same Robot Genome that maps rover wheels maps quadcopter rotors. Five non-negotiable arming safety rules are enforced in every motor command: arm-permitted flag required, pre-arm check must pass, sensor availability verified, 30-second inactivity auto-disarm, emergency stop always registered. Bench-demo verified (props off). Flight commands (takeoff, hover, land) gated behind `--arm-permitted`.

See [docs/mavlink-integration.md](docs/mavlink-integration.md).

---

## Quick Start

```bash
pip install aether-robotics                         # core install (fast, ~80 MB)
export GEMINI_API_KEY=...                           # FREE at aistudio.google.com/apikey
aether --mode agent                                 # talk to your robot

pip install 'aether-robotics[rag]'                  # optional: vector memory retrieval

aether --calibrate                                  # first-time hardware setup
aether                                               # full-screen TUI (v4.2.0)
aether --mode agent                                 # classic REPL — talk to your robot
aether --mode autonomous --goal "map out the room"  # hand it a goal and step back
aether analytics                                    # local usage dashboard
```

Calibration walks every safe GPIO pin (BCM 4–27, excluding I2C/SPI/UART), pulses each with a positional servo sweep, and prompts you to label what moved. The result is a `physical_map` saved in your calibration profile and loaded automatically at next startup.

If AETHER detects a known HAT board during calibration, it loads the matching Tier 1 adapter and registers capabilities automatically. For unknown boards, it offers to generate one.

Once calibrated:

```
objective> move the wheel forward for 5 seconds
[PLAN]  [LLM]  servo_cont_wheel
[EXEC]  servo_cont_wheel(speed=50, duration=5)
[OK]    success=True  (5009ms)
```

---

## What AETHER Does That Others Don't

- **Discovers the robot.** Calibration walks every GPIO pin, you label what moved, AETHER builds a `physical_map` with BCM pin, device type, and action label for each actuator (unlike ROS/Viam where you configure drivers manually before the system knows what hardware exists).
- **Publishes the Robot Genome.** Every calibrated robot gets a versioned `robot.json` identity card — a stable, portable description of its locomotion class, actuators, and sensors. Capabilities are derived deterministically from the genome (same hardware = same capability set), so skills written for a `differential_drive` genome run on every matching robot without wiring changes. See [docs/robot-genome-v1.md](docs/robot-genome-v1.md) for the published schema.
- **Speaks natural language.** Type plain English; the LLM planner resolves intent against your robot's genome capabilities and dispatches the correct actuator — BCM pin pre-filled, servo type resolved, direction inferred (unlike ROS action servers that require typed message structs and correct namespace knowledge).
- **Detects faults in real time.** DRL-First Hybrid FDIR achieves SFRI 69.99, 100% detection rate, and 100% recovery rate over 6,023 real-hardware steps (unlike threshold-rule systems that require manual parameter tuning per platform and miss novel fault signatures).
- **Improves through use.** Correction traces are logged per step; operational memory and sim-to-real action transfer are on the roadmap (unlike static planners that repeat the same failure mode without feedback).
- **Drives drones via MAVLink.** The same genome that maps your rover's wheels maps your quadcopter's rotors. Plug in a MAVLink FC (INAV / ArduPilot / PX4), calibrate once, and type plain English — AETHER handles arm, motor test, and attitude commands via the same natural-language interface as every other robot it knows. See [docs/mavlink-integration.md](docs/mavlink-integration.md).
- **Adapts to unknown HATs automatically.** Plug in a SunFounder PiCar-X or PiDog, Freenove 4WD/Mecanum, Adeept, Waveshare, Yahboom, GoPiGo3, or any custom driver board and AETHER figures it out during `--calibrate`. Ten pre-built Tier 1 adapters cover the most common kits. For anything unknown, Tier 2 reads the manufacturer's Python library and generates a validated adapter using Claude — subject to static safety rules, a sandboxed dry-run, and a mandatory hardware behavioral test before it's accepted. Tier 3 collects control snippets interactively when no library is available. Generated adapters are stored locally at `~/.aether/adapters/` and load automatically on future runs. See [docs/auto-adapter-system.md](docs/auto-adapter-system.md).

### Robot Genome

```bash
aether --genome show
```

```
Robot Genome
  ID:         f47ac10b-...
  Locomotion: differential_drive
  Hash:       a3f1d92e...

Capabilities (6)
  drive_backward  drive_forward  emergency_stop  stop  turn_left  turn_right
```

The genome is stored at `configs/robot.json` and auto-migrates from `physical_map` on
first load of a v3.4.x profile. Skills declare `SKILL_REQUIRES = ["drive_forward", ...]`
and AETHER rejects skills the robot's genome cannot satisfy — before any hardware moves.
Full schema, derivation rules, and skill authoring guide: [docs/robot-genome-v1.md](docs/robot-genome-v1.md).

---

## Commands

### Modes

| Flag | Description |
|------|-------------|
| `--mode sim` | Simulation with fault injection against a virtual robot (default) |
| `--mode agent` | Interactive LLM-planned objectives on live hardware; prompts for input |
| `--mode realworld` | Continuous live-hardware FDIR loop — camera + system sensors, no planner |
| `--mode server` | HTTP API on `--port`; accepts `POST /objective` and `GET /health` |
| `--mode autonomous` | High-level goal in, self-directed sub-task decomposition and execution out (v4.0.0) |

### Useful Flags

| Flag | When to use |
|------|-------------|
| `--calibrate` | First-time setup — walks every GPIO pin, you label what moves; loads or generates HAT adapters |
| `--recalibrate` | Re-run the full pin walk (e.g. after wiring changes); skips known-empty pins; preserves `robot_id` |
| `--auto-calibrate` | Headless calibration with no interactive prompts |
| `--genome show` | Print the loaded robot genome (locomotion, actuators, capabilities) and exit |
| `--task "objective"` | Task description for `--mode sim` |
| `--schedule "..."` | Scheduled runs: `"every 30s: scan environment"`, `"for 5min: obj"`, `"until 22:00: obj"` |
| `--continuous` | Run `--mode realworld` indefinitely until Ctrl-C |
| `--robot {rover_v1,drone_v1}` | Robot config for `--mode sim` (default: `rover_v1`) |
| `--faults {disabled,enabled,heavy}` | Fault injection level for sim/realworld (default: `disabled`) |
| `--scenario TEXT` | Sim scenario: `simple`, `obstacles`, `imu_fault`, `battery`, `compound`, `fault_heavy` |
| `--max-steps N` | Max steps per episode (default: 300) |
| `--seed N` | Random seed for reproducible sim runs (default: 42) |
| `--render` | Print ASCII state render at each sim step |
| `--plots` | Generate matplotlib SFRI/metrics plots after a sim run |
| `--no-learning` | Freeze PPO weights — useful for controlled benchmarking |
| `--port N` | Port for `--mode server` (default: 8080) |
| `--auto-install` | Install missing Python packages without prompting |
| `--auto-update` | Pull the latest version without prompting |
| `--no-install` | Skip the package-install prompt entirely |
| `--no-update` | Skip the update check |
| `--no-color` | Disable colored/formatted output (also via `AETHER_NO_COLOR=1`) — for CI, scripts, log parsing |
| `--verbose` | Enable debug logging |

### Autonomous Mode Flags (v4.0.0+)

| Flag | When to use |
|------|-------------|
| `--goal "..."` | The high-level goal (omit to be prompted, e.g. `"map out the room"`) |
| `--time-budget SECONDS` | Max wall-clock time for the goal (default: 300) |
| `--resume` | Continue the saved goal (paused or timed-out) |
| `--status` | Print current goal state and exit |
| `--history` | Show completed goals |
| `--clear` | Drop the active goal (asks y/N) |

### MAVLink Flags (v3.6.0+)

| Flag | When to use |
|------|-------------|
| `--arm-permitted` | Enable arming and flight commands for MAVLink robots; required before any motor spins |

### Auto-Adapter Flags (v3.7.0+)

| Flag | When to use |
|------|-------------|
| `--generate-adapter-from PATH` | Offline mode: read vendor library at PATH, call Claude, validate, print summary; does not require hardware |
| `--save-to PATH` | Save the output of `--generate-adapter-from` to PATH instead of `/tmp/` |
| `--no-auto-adapter` | Disable Tier 2/3 adapter generation; Tier 1 pre-built adapters still load normally |

### Self-Healing Subcommands (v3.10+)

| Command | What it does |
|---------|--------------|
| `aether doctor` | Read-only diagnostic report (system, install, API key, hardware, adapter, file system, recent boot). Exit 1 on any failure. |
| `aether kits list` | List all 10 Tier 1 kits + Tier 2/3 notes |
| `aether kits info <kit>` | Detection logic, tools, locomotion class, vendor library, and install scope for one kit |
| `aether kits install <kit>` | Guided installer (per-`sudo`-step consent) from an auditable YAML recipe |
| `aether --fix` | Describe an issue in English → AETHER proposes a value override or code patch, drives the robot to verify, then keeps or rolls back |
| `aether --fix --list` / `--history` / `--revert <key>` / `--revert-all` | Inspect/revert value calibration overrides (`~/.aether/overrides.json`) |
| `aether --fix --list-patches` / `--review <hash>` / `--apply <hash>` | Inspect, review (diff), or apply a proposed code patch |
| `aether --fix --revert-patch <hash>` / `--revert-all-patches` / `--reapply-all` | Roll back code patches from backup, or re-apply after a pip upgrade |

### Analytics Subcommands (v4.1.0+)

| Command | What it does |
|---------|--------------|
| `aether analytics` | Summary dashboard: sessions, top tools, adapters, `--fix` + autonomous stats, recent errors |
| `aether analytics --sessions` / `--tools` / `--errors` / `--autonomous` / `--fix` | Drill into one slice (`-n N` to limit) |
| `aether analytics --export FILE` | Dump all local analytics as JSON for external tools |
| `aether analytics --clear` | Clear local analytics data (asks y/N) |
| `aether analytics --enable-telemetry` / `--disable-telemetry` / `--telemetry-status` | Manage strictly opt-in anonymized telemetry |

---

## Supported Hardware

**Tested and working**

Raspberry Pi 4 Model B · USB camera or picamera2 · GPIO servos (positional or continuous-rotation) · SSD1306 OLED over SPI · Anthropic API (LLM planner and vision)

**10 HAT boards / kits with Tier 1 adapters (v3.9.7)**

| Board | Detection | Capabilities |
|-------|-----------|--------------|
| SunFounder PiCar-X | `picarx` importable or I2C 0x14 | forward · backward · turn · camera pan/tilt · distance |
| SunFounder PiDog | `pidog` importable (Robot HAT 0x14) | quadruped walking gait · head pan/tilt · distance |
| Freenove 4WD | `Motor` importable | 6 motion tools |
| Freenove Mecanum | I2C 0x40 + `Motor` | 8 motion tools including strafe left/right |
| Adeept HAT v3 | `adeept` importable | 6 motion tools |
| PCA9685 generic | I2C 0x40 + `Adafruit_PCA9685` | 6 motion tools |
| L298N direct GPIO | GPIO available, no I2C, `RPi.GPIO` | 6 motion tools |
| Waveshare motor driver | `waveshare_motor` importable or I2C 0x47 | 6 motion tools |
| Yahboom robot HAT | `YB_Pcb_Car` importable or I2C 0x16 | drive · turn · camera pan/tilt · distance |
| GoPiGo3 | `easygopigo3` / `gopigo3` importable (SPI) | drive · turn · ToF distance |

Install any of these with `aether kits install <name>` (recipes shipped for PiCar-X, PiDog, Freenove 4WD, Yahboom, GoPiGo3).

**MAVLink flight controllers (bench-demo in v3.6.0)**

INAV · ArduPilot · PX4. Auto-detected during `--calibrate`; arm, motor test, and attitude commands verified at bench (props off). Flight commands gated behind `--arm-permitted`.

**Should work, unverified**

Pi Zero 2 W · Pi 5 (requires `rpi-lgpio` in place of `RPi.GPIO`)

**In development**

Arduino/ESP32 serial bridge · multi-robot coordination

---

## Architecture

```
  User input (plain English)
        │
        ▼
  ToolDiscovery ──────────────► physical_map + robot.json genome
        │                                  │
        │                    AdapterResolver (Tier 1 → 2 → 3)
        │                         └── ~/.aether/adapters/  (persisted)
        ▼                                  │
  LLMPlanner ◄──── genome capabilities injected into planner context
        │
        ▼
  NavigationEngine  (L1 camera / L2 GPIO / L3 MAVLink)
        │                         │
        ▼                         ▼
  Hardware (servo/motor/FC)  FaultAgent (PPO, 15-dim obs → fault class)
                                   │
                             detect · recover · log
```

---

## Benchmarks

Real-hardware deployment: Raspberry Pi 4, GPIO servos, live camera, Anthropic API planner.

| Metric | Value | Conditions |
|--------|------:|------------|
| SFRI | **69.99** | 6,023 steps, real hardware |
| MTTR | **1.35 steps** | Mean time to recover from injected fault |
| Detection rate | **100%** | 0 misses, 0 false positives |

> SFRI (Stability Fault Recovery Index) = 35×DR + 25×(1 − MTTR/max\_steps) + 10×RR − 30×FPR. Range 0–70; higher is better.

---

## Roadmap

- ✓ **Phase 1 — Reverse Engineering** — GPIO pin walk, physical_map, LLM-planned hardware control
- ✓ **Phase 2 — Robot Genome** — versioned `robot.json` schema, deterministic capability derivation, skill portability ([docs/robot-genome-v1.md](docs/robot-genome-v1.md))
- ✓ **Phase 3 — Auto-Adapter System** — Tier 1 pre-built adapters (now 10), Tier 2 LLM-generated + validated, Tier 3 guided wizard; `--generate-adapter-from` offline mode ([docs/auto-adapter-system.md](docs/auto-adapter-system.md))
- ✓ **Phase 3.5 — Self-Healing** — `aether doctor`/`kits` diagnostics, and `aether --fix` value-calibration overrides + verified code patching (v3.10)
- ✓ **Phase 3.6 — Multi-HAT** — multi-pass discovery + multi-adapter loading with robot.json v1.1 (v3.11.0), tool namespacing + primary-adapter routing (v3.11.5), structured `--calibrate` wizard with named servos (v3.11.6), GPIO-conflict detection (v3.11.10)
- ✓ **Phase 3.7 — Autonomous Mode** — LLM goal decomposition, self-directed execution loop with adaptive replanning, resumable persisted goals (v4.0.0)
- ✓ **Phase 3.8 — Analytics + UX** — local usage analytics with strictly opt-in telemetry (v4.1.0), rich terminal UI with plain-text fallback (v4.1.5)
- **Phase 4 — Vision-Language Grounding** — "follow the orange cone", scene-grounded navigation, in progress
- **Phase 5 — Operational Memory + Sim-to-Real Action Transfer**
- **Phase 6 — Multi-robot coordination**

---

## Early Access

AETHER is in early access. We're working with a small number of robotics teams and researchers to refine the platform. Email chahelpaatur@aether-robotics.com for access.

---

## Citation

```bibtex
@software{aether2026,
  title   = {AETHER: Autonomous Operating System for Robots},
  author  = {Paatur, Chahel},
  year    = {2026},
  version = {5.0.1},
  url     = {https://aether-robotics.com},
  note    = {DRL-First Hybrid FDIR with physical-map calibration, multi-provider LLM planning (free-tier capable), MAVLink integration, multi-HAT Auto-Adapter System, self-healing calibration/code patching, autonomous goal decomposition, a full-screen TUI, memory retrieval, and safety-validated parallel execution},
}
```

---

AETHER is proprietary software in early access. All rights reserved. © 2026 Chahel Paatur.
