Skip to content

Roadmap: the first one reached its target, so this is the next one - #158

Merged
pierre-rouanet merged 4 commits into
mainfrom
roadmap-rewrite
Aug 27, 2026
Merged

Roadmap: the first one reached its target, so this is the next one#158
pierre-rouanet merged 4 commits into
mainfrom
roadmap-rewrite

Conversation

@pierre-rouanet

Copy link
Copy Markdown
Member

M1 to M4 were a sequence with one destination — a robot that walks, updates itself over the air, and cannot be bricked doing it. That arrived. The work left is independent tracks with different risks and no ordering between them, so the milestone numbers stop being a sequence and the order of work becomes its own section.

Three new milestones

M7 — knowing what a board is doing. A duck in someone else's hands will develop a fault, and today the answer to which part is a developer reading a journal over ssh. No design, on purpose — it is framed as the four questions it owns: a support bundle (nothing collects health, versions, journal, update history and unit states together), a hardware pass/fail a non-developer can run, history for the intermittent faults a snapshot cannot show, and the overlap with #52 — whose crash record, flushed to /var/lib outside release dirs, is exactly the durability a hardware fault report needs. The two should be decided together rather than growing two answers. M4's two unmeasured numbers land here.

M8 — the model channel. Every policy ships inside the daemon artifact today, so a new gait needs a daemon release and a policy trained on a laptop reaches a duck only through CI. The engine was designed for the other arrangement and most of it is already built: the hf_hub source verifying our own minisign (HF signs nothing for us), a model as an ordinary component with its own version line, install dir, rollback target, pin and known-bad history, action = "reload" implemented, and xtask sign signing any directory.

What is missing is at the two ends: robotd has no SIGHUP handler and no way to swap an ort session under a running 50 Hz loop; nothing publishes a bundle (xtask package --channel model-walk checks --version against a crate version a model does not have); model_api is designed and unimplemented; and publishing a policy is a third signing key, not a reuse of release-1 or team.dev. One decision comes first — whether the release keeps its copies as a floor that a Hub component overrides, or the network becomes a hard dependency for a robot that walks.

M9 — the autonomous brain. The biggest untracked gap, with the shape argument that matters: presence, mood and the shared beat are inputs to one brain, not modes beside it. The chorale and the theremin grew as modes because there was nothing to hang them on, and the chorale is now 55 KB of robotd.

What changed in the existing ones

M4 closes, with its log criterion rewritten. It required logs to survive a power cut; /var/log is a zram device and the durable record is the update history under /var/lib, so the criterion asked for a property this project decided not to have (deploy/README.md records the decision). What is left is one journalctl --list-boots on a board.

M5 gains the Python client question — whether one client covers both transports, or the WebSocket surface alone is what a script wants — and splits privacy in two: consent is a mediad session-layer change and is unblocked, the indicator needs an LED under software control that does not exist and should be asked of the hardware rather than parked here.

M6 is reordered by lead time rather than difficulty, and promotes what was an M2 footnote: a private repo's release assets are reachable with a token and a customer robot has none, so as things stand robots in the field cannot download anything (updater-design.md §6.1). With preorders open, that is the longest-lead decision on the page — it changes where release.yml publishes and what updater.toml on every shipped robot points at.

Also

pierre-rouanet and others added 4 commits August 26, 2026 17:30
M1 to M4 were a sequence with one destination — a robot that walks, updates
itself over the air, and cannot be bricked doing it. That arrived at 0.9.x. The
work left is independent tracks with no ordering between them, so the milestone
numbers stop being a sequence and an "order of work" section carries that
instead.

Three milestones are new:

- **M7 — knowing what a board is doing.** A duck in someone else's hands will
  develop a fault, and today the answer to *which part* is a developer reading a
  journal over ssh. Framed as the questions it owns rather than a design: a
  support bundle, a hardware pass/fail a non-developer can run, history for
  intermittent faults, and the overlap with #52 — whose crash record is exactly
  the durability a fault report needs. M4's two unmeasured numbers land here.
- **M8 — the model channel.** Policies ship inside the daemon artifact today, so
  a new gait needs a daemon release. The engine was designed for the other
  arrangement and most of it exists: the `hf_hub` source verifying our own
  minisign, per-model components with their own version line and rollback, and
  `action = "reload"`. What is missing is at the ends — `robotd` cannot reload,
  nothing publishes a bundle, and publishing a policy is a third kind of signing
  key.
- **M9 — the autonomous brain**, with the shape argument that matters: presence,
  mood and the beat are inputs to one brain, not modes beside it.

M4 closes with its log criterion rewritten. It asked for logs surviving a power
cut; `/var/log` is zram and the durable record is the update history, so the
criterion was asking for a property this project decided not to have. What is
left of it is one `journalctl --list-boots` on a board.

M5 gains the Python client question — whether one client covers both transports,
WebSocket for control and snapshots and WebRTC only for a stream — and splits
privacy in two, because consent is unblocked and the indicator waits on hardware
that does not exist.

M6 is reordered by lead time rather than difficulty, and promotes what was an M2
footnote: a private repo cannot serve the fleet, so as things stand robots in
the field cannot download anything. With preorders open that is the longest-lead
decision on the page.

Assisted-by: Claude:claude-opus-5[1m]
**The repository goes public**, which closes §6.1. It was the longest-lead item
in M6 on the grounds that a robot shipped pointing at the wrong host cannot be
updated; going public means there is no second host and no token in an image.
The section keeps the options and the reasoning rather than deleting them —
an artifact-only public repo is the fallback if the source ever has to close
again — and records what going public does *not* change: the engine's API path
is already the one path for private and public alike.

What it does leave is a budget, written down where someone will hit it.
Anonymous GitHub API requests are capped at 60 an hour per IP and a home robot
has no token, so its checks spend from the pool shared by everything behind that
address. One duck at `check_interval = "6h"` is nowhere near it; a room of
twenty on one wifi is not. Not a correctness problem — `http.rs` already reads
403 and 429 as "come back later" — but a reason to fetch bytes through
`browser_download_url` once public, since object downloads spend nothing from
that pool.

**Models: browsing is a trust decision, not a plumbing one.** The ask is basic
policies of our own plus an easy way to look at and download others, and the
second half is what needs deciding: every artifact the engine installs is
verified against a trusted key, and a stranger's model is signed by nobody this
robot trusts. Recorded as three things to settle inside M8 — curated org or an
explicitly unverified path that is off by default and refused on a customer
robot the way dev keys are; the `obs[1,61] -> actions[1,14]` shape check becoming
a hard gate whoever signed it; and the fact that makes it allowable at all,
which is that the safety layer holds the only write handle to the bus.

Slots stay fixed and sources do not: repointing `walk` at another repo keeps
rollback, pin and known-bad per slot, where letting arbitrary components appear
at runtime would mean the config is no longer the authority on what a robot may
run.

Assisted-by: Claude:claude-opus-5[1m]
…n broken

The lean is that they leave and the updater fetches them from the Hub. Written
down with the edge that decision has, because it is not the obvious one: a
policy that was wanted and could not be loaded reports *unhealthy*, so a board
with no models installed would fail the health gate on every subsequent daemon
update and roll back — an update loop caused by a file the update could not
have supplied.

`HealthResult::degraded` already answers it, and was built for this exact shape:
a condition that belongs to the board rather than to the release being gated,
where reverting cannot fix it and only churns the boot counter. Which model
components are installed is that, so the gate commits and health names the
missing policy.

The other consequence — no gait on a flashed board with no network — goes where
the same dependency already lives: provisioning installs the bootstrap set, as
it already installs the ONNX runtime and the GStreamer plugins. That also leaves
exactly one source for a policy at runtime, so there is no precedence rule to
write between a release copy and a Hub copy.

The floor-plus-override alternative stays recorded with what it buys, since the
decision is a lean rather than a settlement.

Assisted-by: Claude:claude-opus-5[1m]
Everything the rewrite added about going public was written in the future
tense — "the repository goes public", "until it does", "until this week" — and
all of it reads wrong the day the flip happens. The decision is what these docs
record, so they now record the decision: *publish this repository*, phrased so
it is true on both sides of the switch and needs no second pass afterwards.
§6.1's heading loses its "so this one goes public" for the same reason.

`install-dev.md` had the sharper version of the problem: it walks a reader
through baking a GitHub token onto a board and `chmod 600`-ing it, listed as a
flat prerequisite. Once the repo is public that credential buys nothing but a
higher rate limit, and a reader following the steps unchanged would put one on a
board for no reason. The prerequisite and the by-hand section now say when a
token is actually needed — while the repo is private, or for the anonymous API
budget §6.1 documents. `provision-board.sh` already said this ("only while the
repository is private"); the docs were behind the script.

Two smaller things a public reader would hit:

- The link to `microduck_rl` 404s for anyone outside the org, and it is M8's
  central artifact. De-linked and named as private, rather than pointing a
  stranger at a page they cannot open.
- M6 no longer carries a delivery estimate. "Preorders are open" is the whole
  reason the section orders by lead time; "shipping is a few months out" is a
  schedule commitment in a versioned file, doing no work in the argument.

Not touched here, because it is a decision rather than an edit: `ci-setup.md`
states in bold that anyone with push access can read the release signing key,
and why no protection rule can be turned on. The roadmap links to it. That goes
public in the same flip and is worth settling first.

Assisted-by: Claude:claude-opus-5[1m]
@pierre-rouanet
pierre-rouanet merged commit a60e87c into main Aug 27, 2026
1 check passed
@pierre-rouanet
pierre-rouanet deleted the roadmap-rewrite branch August 27, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant