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 loginshowed raw control codes such as←[1maround 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_COLORnow also applies to the lines that ignored it until now. trialone loginshows 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 loginwithout 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-browseronly 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>andtrialone login --pastestill 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 fromtrialonealone, and the rest) first checks that this terminal is signed in withtrialone loginand that the account is approved. Access is by request at trialone.dev/request-access.login,whoami,updateand--helpwork 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 loginnames 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
fullby 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
fullby 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 whosemetadata.yamlis missing, if it holds MCAP: that is what a recording cut off by a crash looks like.trialone install-service --installinstalls the watcher as a user service (no sudo), starts it, and checks that it actually stays up instead of restarting every 30 s.trialone attachrecords 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 watchpushes when you are logged in, likediagnose(--no-pushto 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-rawand--no-pushondiagnoseandwatch;--healthy,--project,--incident-atand--explain-budgetonwatch;--no-pushonverify.
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'smeta/modality.jsonfield names).trialone diagnose lerobot/pushttakes 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. Needspip 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.successis used as the reference only when the dataset holds both outcomes: onlerobot/pushtit is true in none of the 206 episodes. - Where a dataset pairs
actionandobservation.stateper 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 aschange_class: policy, and for coding agents as routepolicyinget_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
--projectcan 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 verifyno 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 writestime) 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, andtrialone learntakes 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
memberoradminunder 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. Needsweb/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(orkind: serialin 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). Needspip install "trialone[serial]".--followreads Serial Plotter lines; before,current:1.2,temp:40was 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_windowper finding,fault_group(the family of a finding) androute(hardware / software / parameter / combined / unknown), andt_from/t_towherever 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.
/updatecould uninstall trialone and leave nothing behind. A user ran it, saw one yellow line:Uninstalling trialone-0.4.12:: and afterwardstrialonewas 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./updatealso 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, eachfile:linein 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:266is 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, andTRIALONE_KEIN_QUELLTEXT=1switches it off entirely. - A crash backtrace is now a located fault. Measured on
navigation2#5710without any model call: the report said "Crash site (first user frame): … at costmap_2d.cpp:266" while the recipe'sfiles and linesfield 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/6while the answer body said: providerDeepInfra, errorengine_overloaded, limit sourceupstream_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
/modelnamed 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 readprovider anthropic, and worse:/modelthen fetched Anthropic's catalogue, your pick was stored asclaude-sonnet-5, and the run went to OpenRouter, where that model is calledanthropic/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=openrouterwith no OpenRouter key used to fall through silently to whatever other key existed./modelnow stops with the reason instead of fetching a catalogue that cannot exist. - Signing in is the switch: you no longer type
--pushevery time. Once/loginconnects an account, diagnoses in the window push by themselves;--no-pushstays 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
/updateclears 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
/updatesucceeds. - The logo no longer disappears in normal-height terminals with many recordings: the recording list shrinks first (down to three rows,
/lshas 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=1turns it off). If a newer version exists, ONE red line names it, with the fix beside it: type/update. /update(window) andtrialone 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/loginin the window) connects to trialone.dev: the key is checked against the site before it is stored.--pushondiagnoseandverifyposts 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.