Changelog

Nothing changes silently.

A diagnostic tool must not change behaviour silently: the same log through two versions can produce different reports, and you deserve to know whether that difference is us or your robot. Every entry answers one question: what will read differently after this upgrade. Every diagnosis records the engine that produced it (measured_with in the JSON), so an old report never has to be guessed about.

0.4.17

15 September 2026

Signing in reads cleanly in every terminal

  • In cmd and other Windows consoles, trialone login showed raw control codes such as ←[1m around the address and the code. Every command now switches the console to show color before it prints, and prints plain text where the console cannot. NO_COLOR now also applies to the lines that ignored it until now.
  • trialone login shows the one-time code first and opens trialone.dev/cli-login when you press Enter, so the browser no longer covers the terminal you read the code from. On a robot over SSH, in a script, or with --no-browser, it prints the address and waits, as before.
  • After signing in, it names the file the key is stored in and where to revoke it.

0.4.16

15 September 2026

Signing in no longer means copying a key

  • trialone login without a key shows a short code and the address trialone.dev/cli-login. Enter the code there while you are signed in and approve it: the terminal receives its key within seconds, and the key is checked before it is saved, as before. On a robot over SSH, open the page on your laptop; --no-browser only prints the address. The page never takes the code from a link, so a link someone sends you cannot sign their terminal into your account.
  • trialone login <key> and trialone login --paste still take a key from Settings, CLI keys, for scripts that hand out keys.

One sentence in the report reads differently

  • When far more operations of a set cross your healthy reference than its false-alarm rate allows, the summary, the finding, the recipe and the next step now name both readings: this set differs from the reference (a later session, a different speed or policy, a changed task), or something on the machine changed since the reference was recorded. They say what decides between the two. Until 0.4.15 they named only the first and suggested adding such runs to the reference, which for a part that jams in every operation would have built the fault into it. The classification (CANDIDATE, zone LOOK) is unchanged.
  • The probability in that sentence reads "less than 1 in a billion" instead of a number like "1 in 999,999,999,999,999,872".

0.4.15

15 September 2026

Two weeks of changes. Read the first block before you upgrade a robot: more of what a run measured now leaves the machine by default, and you decide per machine how much.

TrialONE now runs only for an approved account

  • Every command that starts the engine (diagnose, watch, verify, learn, the window you get from trialone alone, and the rest) first checks that this terminal is signed in with trialone login and that the account is approved. Access is by request at trialone.dev/request-access. login, whoami, update and --help work without it.
  • The check asks trialone.dev at most once a day. Without a network, the last confirmation counts for 30 days, so a robot in a hall without Wi-Fi keeps running. Refused: exit code 77.
  • Help, error messages and the update notice now point to the documentation at trialone.dev/docs and to this changelog at trialone.dev/changelog. trialone login names the page where CLI keys are created (Settings, CLI keys).

What leaves the robot now: by default, and how to turn it down

  • History, level full by default. A pushed run now carries a trend of every channel that moved (min/mean/max, 120 points per run; while watching, one point per minute), the minute around each finding (30 s before to 30 s after, 10 points per second), and a packed file with every moving channel at 10 Hz, uploaded to our storage. Up to 0.4.14 only per-channel statistics left the machine. Turn it down per machine under Settings → Robots, or with --history off|incident|full; a flag wins over the setting, and the screen says which applied.
  • Machine state, level full by default. A run records the git commit and the last 50 commit subjects of --project (hash, date, subject: no source code, no diff, no credentials from the remote), and the parameter, launch and URDF files next to it, contents sent once per hash. --state off|code|full, or per machine in the settings.
  • The recording itself: never, unless you switch it on for a machine or pass --upload-raw.

New commands, and a watcher that finally talks to the website

  • trialone learn <folder> reads recordings the robot already made as the reference for the live watcher. No model call, no upload. It also reads a rosbag2 folder whose metadata.yaml is missing, if it holds MCAP: that is what a recording cut off by a crash looks like.
  • trialone install-service --install installs the watcher as a user service (no sudo), starts it, and checks that it actually stays up instead of restarting every 30 s.
  • trialone attach records what this robot is, with a date. trialone mark <recording> says "something just happened" about a recording.
  • trialone push <diagnosis.json> sends a diagnosis that is already on disk to your account, without analysing again: for a run whose push failed. Exit code 3 if it still does not arrive.
  • trialone watch pushes when you are logged in, like diagnose (--no-push to stop). Until now it pushed only with --push, and the service file from our own pilot guide did not pass it: a watcher set up exactly as we described sent nothing. A logged-in watcher now also checks in every minute (the website can tell "silent" from "stopped by hand"), takes jobs from the website (build a reference, verify a repair), and says goodbye when it is stopped.
  • New flags: --history, --state, --upload-raw, --no-upload-raw and --no-push on diagnose and watch; --healthy, --project, --incident-at and --explain-budget on watch; --no-push on verify.

Teams that train policies: LeRobot datasets, and "policy or robot?"

  • trialone diagnose <dataset folder> reads a LeRobot dataset (v2.x and v3.0; the format LeRobot records, and the one openpi/pi0 and NVIDIA GR00T train on, including GR00T's meta/modality.json field names). trialone diagnose lerobot/pusht takes a Hugging Face dataset id: the copy LeRobot already has on this machine first, otherwise metadata and only the data files that are read. Videos are never read or downloaded, and the report names the camera streams it skipped. Needs pip install "trialone[lerobot]" (pyarrow, huggingface_hub).
  • Each episode is one run of the task; --healthy <training dataset> measures rollouts against the data the policy learned from. next.success is used as the reference only when the dataset holds both outcomes: on lerobot/pusht it is true in none of the 206 episodes.
  • Where a dataset pairs action and observation.state per joint, every flagged episode says which side it is on: POLICY, NOT THE ROBOT (the arm followed, the commands differed), THE ROBOT, NOT THE POLICY (ordinary commands, the arm did not follow), or that the two cannot be separated here. A policy finding has its own route, policy, and goes to the person who trains the policy instead of a technician or a coding agent: on the website under Fix, in the recipe as change_class: policy, and for coding agents as route policy in get_issue.

The live watcher reads differently

  • Three zones per window. FAULT is evidence that stands on its own (a device that went silent, a cadence shared by many channels breaking together, a sibling pair moving in opposite directions, a reference with a measured false-alarm rate). LOOK is everything else that fired: it comes with something to watch instead of a DO:. The report says "N of M … undecided".
  • An all-clear from the model is no longer a veto. The task stays and is marked NOT cleared.
  • It names the part, uses a learned reference, and with --project can land on a file.
  • Silent failures that changed answers, fixed: a whole node dying was invisible; clock jumps were confused with transport reordering; the live CAN lane delivered zero channels; three live detectors received the whole 30-minute buffer instead of their window, so a jam got harder to see the longer the watcher ran; a machine standing still could acquit a jammed joint.

Verdicts and tables that read differently

  • trialone verify no longer says FAIL when the original findings are gone and all that is new are CANDIDATEs (measured without a false-alarm rate): that is now INCONCLUSIVE, exit code 2, with the reason. Measured on our own demo pair: the recording after the repair is byte for byte the healthy reference, and the old verdict was "FAIL: 1 gone, 0 still present, 4 new". A finding that is still present, or a new one that is not a mere candidate, is FAIL as before.
  • A table whose time column is not called timestamp (a UR export writes time) now gets its cycle time without --time-col, and the time column is no longer measured as if it were a signal. This can change which operations of such a table rank highest; the report says which column it took.
  • A CSV saved by German Excel (; between fields, decimal comma) is read as the table it is, instead of as one column.
  • A folder of separate MCAP recordings (a night of simulation runs, each file its own run) is no longer read as ONE recording. Parts of one rosbag2 recording share the name <name>_<n> and follow each other in time; anything else is diagnosed file by file, and trialone learn takes such runs one by one. Measured: three 84 s runs in one folder had come back as 21 operations instead of 63, and the jammed joint of the second file as four weak candidates without a file.
  • On the website, a live finding in the LOOK zone no longer sends a Slack or Discord alert, even at HIGH severity; FAULT does. The finding page now shows the zone next to the severity.

Teams on the website

  • A workspace can have members. The owner invites people by email as member or admin under Settings > Team; everyone in the workspace sees the same robots, issues and runs, and a robot or coding-agent key that a member creates reports into the workspace, not into a private account. The owner alone manages billing; owners and admins manage people, robots and connections. Needs web/supabase/team.sql; without it every account works alone, as before.
  • Pro is sold per seat again (1 to 25). An open invite holds a seat; the owner adds or gives up seats on the Team page, charged pro rata.
  • People appear with their name and picture (from their profile or their Google or GitHub sign-in), not with their email address.
  • Invites arrive without anyone forwarding a link: our own email when email sending is set up, otherwise the invite email of the sign-in service (it creates the account and signs the person in), and for an address that already has an account the invite waits inside TrialONE, above every page. The screen says which way it went; the link to copy is only the fallback. An invite can be declined.
  • An issue shows who is on it. Assign it to yourself or a teammate from the menu next to its state; setting In repair assigns you when nobody is. The issue list shows the person and filters by Assigned to me, Nobody yet or a person.
  • Every issue keeps what people did on it, with their name: assigned, state changed (with the note), commented, asked a robot to verify, opened a GitHub issue, shared, a repair claimed through a coding agent. Comments can be written on the issue page. Needs web/supabase/team_aktivitaet.sql; without it everything still works and the page says that the history is not recorded.
  • The issue page carries the connections: create a GitHub issue (afterwards a link to it), share to Slack or Discord with a line of your own, open the recording in Foxglove when one is uploaded.
  • Ask answers questions about the team: who is in it and who is only invited, who is assigned to which issue and who assigned it, what someone did. Issues have no numbers: asked about "issue 4567", Ask says that no issue starts with 4567 instead of answering about another one. Ask also answers before the first run is pushed; until now it refused with "Nothing to answer from yet".
  • Ask counts its daily ceiling per seat, so a team does not share the limit of one person.
  • The Kind and Fix filters on the issue list take effect at once; before, the list could keep showing the previous selection until another filter changed.

Microcontrollers: Arduino, Teensy, ESP32

  • An ESP32 crash report is read, not skipped. Text from ESP-IDF, the Arduino core for ESP32 and micro-ROS on ESP32 (Guru Meditation Error, abort(), assert failed:, brownout, stack and heap faults, task watchdog) gets its own decoder. Until now a RISC-V panic was announced as "Cortex-M / NuttX-style" and came back "No decodable fault", and an Xtensa assert with a decoded backtrace came back with no finding at all. The register that names the exception is checked against the panic line; on RISC-V the refused instruction in MTVAL is decoded (a floating-point instruction with the FPU off is named as such). A brownout is routed to hardware, everything else to the coding agent.
  • trialone watch --serial /dev/ttyACM0 (or kind: serial in robot.yaml) watches a microcontroller on its serial port: lines in the Arduino Serial Plotter format (label:value) become channels, and an ESP32 that crashes while watched becomes a work order from its own report. Opening the port does not reset the board (DTR and RTS stay low). Needs pip install "trialone[serial]".
  • --follow reads Serial Plotter lines; before, current:1.2,temp:40 was taken for a CSV header.

Recipes carry more

  • New fields: inventory (every channel with count, rate, range and whether it moved), operations (cycle time as a number), state, history, incident_window per finding, fault_group (the family of a finding) and route (hardware / software / parameter / combined / unknown), and t_from/t_to wherever a finding has a time.
  • When the model ran, its next check reaches the recipe; before, runs that spent minutes in the model could still say "none defined".
  • What a person wrote down on the website ("swapped the connector on the 5th") reaches the interpretation, marked as a claim and never as a measurement.

On screen

  • Under a candidate, the short view shows the measured number instead of a bare "RULED OUT by this recording:" with nothing after it.
  • German words left the English output ("hoch" is now "above"/"below", plus five earlier lines).
  • The report header lost its empty brackets, and verify lines are no longer cut mid-sentence.
  • The refusal to calibrate a false-alarm rate is in English now ("NOT CALIBRATED ...").
  • Without a model, the live watcher states why there is no explanation once per session and then one short line per finding, instead of the same paragraph under every finding.
  • After a diagnosis that goes to a coding agent, the screen also names the way without copying: connect the agent once on the website and ask it for the newest issue.

0.4.14

31 August 2026

Three failures reported from one live session: and the two things that session showed were missing.

  • /update could uninstall trialone and leave nothing behind. A user ran it, saw one yellow line: Uninstalling trialone-0.4.12:: and afterwards trialone was gone (ModuleNotFoundError). Three defects had to line up: the failure sentence was built from pip's last STDOUT line (its progress chatter) instead of its stderr, so the real reason was never shown and the Windows-lock branch could never match; nothing checked afterwards whether the package still existed; and a 300 s timeout could kill pip between the uninstall and the install, where its own rollback does not run. Now: the update is judged by the STATE afterwards (asked in a fresh process, not by scraping text), a package that pip removed and failed to restore is repaired automatically, and if even that fails you get the exact recovery command instead of a yellow aside. /update also installs the version the window advertised, not whatever PyPI calls newest a minute later.
  • The window could stay silent about a new release for a whole day. The version check cached PyPI's answer for 24 h: including an answer that was already known to be wrong (it named a version older than the one installed, which is what happens when you run a hand-built wheel). That cache is now discarded when it is older than your install or was written for a different one, and the interval is 6 h instead of 24 h.
  • A located fault now carries the code, not just the address. When you run with --project, each file:line in the recipe brings the lines around it (13 at most, 200 characters each), and the web view shows them with the hit line marked. costmap_2d.cpp:266 is the right answer and still not an action: it makes you search, open and jump. What this changes about the promise, said plainly: the recording still never leaves your machine: an excerpt is not a recording: but your SOURCE lines now travel with the recipe. The page says how many, and TRIALONE_KEIN_QUELLTEXT=1 switches it off entirely.
  • A crash backtrace is now a located fault. Measured on navigation2#5710 without any model call: the report said "Crash site (first user frame): … at costmap_2d.cpp:266" while the recipe's files and lines field stayed a gap: the address was in the same object, one field away, and the field a coding agent reads was empty. The crash frame and its caller are now resolved in YOUR tree and stated as candidates (the line number comes from the reporter's build, so it is not claimed as proven).
  • HTTP 429 said nothing about why. The screen read openrouter said HTTP 429 -- waiting 60s, then retry 6/6 while the answer body said: provider DeepInfra, error engine_overloaded, limit source upstream_provider_shared_pool: that is the provider's shared pool for that model is saturated, not you are asking too fast, and no amount of waiting fixes it (measured: the model in question has exactly one provider, so there is no route around it, while two other DeepSeek models answered in under two seconds). Such a 429 now retries once briefly and then stops with the provider, the reason and the way out: pick another model. An ordinary 429 keeps the old exponential backoff.

0.4.13

31 August 2026

  • /model named the provider that does not answer. With two keys stored (say Anthropic and OpenRouter), the window resolved "which provider" by which key sits first in the table: it never looked at your explicit choice. So after switching to OpenRouter you still read provider anthropic, and worse: /model then fetched Anthropic's catalogue, your pick was stored as claude-sonnet-5, and the run went to OpenRouter, where that model is called anthropic/claude-sonnet-5. If a report of yours says "ANSWERED BY A DIFFERENT MODEL", this is why. The window now asks the same code the engine asks, so the name on screen is the name that gets the request.
  • A provider chosen without a key is said out loud. TRIALONE_LLM_PROVIDER=openrouter with no OpenRouter key used to fall through silently to whatever other key existed. /model now stops with the reason instead of fetching a catalogue that cannot exist.
  • Signing in is the switch: you no longer type --push every time. Once /login connects an account, diagnoses in the window push by themselves; --no-push stays as the way out for a single run. Forgetting the flag used to cost you silently: the run was missing from the history and the issue counted one occurrence less, noticed days later.
  • The startup screen says whether an account is connected, next to recordings, source tree and model: reading the file only, no network call, so a bad lab WLAN cannot make the start hang.
  • Two new deterministic scans over your source tree, both with a denominator and no model call: which sibling class breaks a contract its family keeps (a missing guard, a base class without a virtual destructor), and which file breaks the naming convention the rest of your tree follows. They claim no fault: an outlier can be deliberate: and they are put in front of the interpreting layer as data rather than offered as a tool it may ignore.
  • The report now states what the layer was TOLD before it answered, read off the prompt that was actually sent. Without that line, "it was told and discarded it" and "it never saw it" look identical in a report, and those are two different repairs.

0.4.12

30 August 2026

  • Resizing the window no longer leaves a second, dead input field on screen. The wipe now waits until the size stops changing, clears, and clears once more a beat later: the second pass catches whatever repainted in between (the first attempt raced prompt_toolkit's own resize redraw and lost by one frame). One field, always. The honest price: what stood above the field is gone after the resize (it would otherwise be a torn copy of itself).
  • A version-check cache entry with a future timestamp can no longer pin the red update line forever, and a successful /update clears the cache so the next start asks reality.

0.4.11

30 August 2026

Fixes from the first live update drill (three window bugs, all reported with screenshots):

  • The update notice lives IN the prompt now and is redrawn with it: in 0.4.9/0.4.10 it was printed from a background thread and immediately painted over, so nobody ever saw it. It clears itself once /update succeeds.
  • The logo no longer disappears in normal-height terminals with many recordings: the recording list shrinks first (down to three rows, /ls has the rest), the logo falls only after.
  • The version is part of the banner itself, in both sizes: no more duplicate v… line.

Known limit, named rather than hidden: minimizing/restoring a maximized Windows console can leave a stale copy of the input area in the scrollback. That copy is dead history the moment you type: the live field at the bottom is the real one.

0.4.10

30 August 2026

Nothing reads differently. This release exists to prove the announcement chain end to end: it was published, and every 0.4.9 window announced it in red within a day: the first release that could not happen silently.

0.4.9

30 August 2026

New: the window keeps you current: without ever updating itself silently.

  • The window shows its version under the banner (v0.4.9): the first question of every support conversation, answered before it is asked.
  • On startup the window checks PyPI in the background (at most once a day, 3-second timeout, silent when offline; TRIALONE_KEINE_VERSIONSPRUEFUNG=1 turns it off). If a newer version exists, ONE red line names it, with the fix beside it: type /update.
  • /update (window) and trialone update (terminal) fetch the newest version in one step: always on your command, never automatically: a diagnostic tool that rebuilds itself between two diagnoses would change answers without consent. After updating, restart the window; the running session keeps its old code and says so.
  • This changelog ships beside the wheel, and a release without an entry here refuses to build.

0.4.8

30 August 2026

New: your terminal can hand results to your account.

  • trialone login (also /login in the window) connects to trialone.dev: the key is checked against the site before it is stored.
  • --push on diagnose and verify posts the nine-field recipe to your account and prints a private link. Only the recipe travels (a few KB, including the log lines it cites as evidence); the recording never leaves your machine. A dead network is one sentence, never a failed diagnosis.
  • Every diagnosis now carries a fingerprint: the group identity that lets the website say "this is the same fault as last Tuesday, ×3" instead of listing three unrelated runs. Built only from deterministic finding identities, never from model prose.

Reads differently after upgrading:

  • Crash reports from stripped release binaries now name the true crash site (frame 0) instead of the first frame that happened to carry a line number: previously that could point ten frames deep into framework code (tf2, rclcpp) that has nothing to fix.
  • The caller chain under a crash follows the stack of the crashing thread; it no longer mixes frames from other threads of a thread apply all backtrace.
  • Plain-text reports of a crash ("Segmentation fault in X::y" in an issue text or console log) are diagnosed; previously some produced a two-line report and stopped.
  • Start scripts (rc.*, the PX4/NuttX convention) are visible to the source search and quoted as evidence: a wrongly configured driver flag is now findable where it is set.
  • File paths in reports resolve the way people write them (amcl_node.cpp, nav2_amcl/src/amcl_node.cpp, from the workspace root): the report no longer claims your own file "does not exist in the project" because of a path prefix.

0.4.7

29 August 2026

  • First release on PyPI: pip install trialone.
  • Fixed: thinking models (Claude Sonnet/Opus) received a token budget too small to answer: a paid model call could return nothing. If 0.4.6 gave you empty model explanations, this was why.
  • Diagnoses run at temperature 0: the same log and the same tree now produce the same answer.

Pre-PyPI, distributed by hand as wheel files. Reports from these versions carry their version in measured_with.