The LLM Read Every Register. The Answer Was a Smell.
Here is a situation you have probably been in, or will be soon.
You hand an AI agent access to a system — a server, a codebase, a production database — and ask it to work out why something is broken. It reads everything. It runs commands you wouldn’t have thought to run. It comes back with a diagnosis written in complete, confident sentences.
How do you know which parts of that to trust?
I got an unusually clean answer to that question, because the system I handed over was a physical one.
When an agent debugs your web app, the boundary between what it can observe and what it can’t is fuzzy. When it’s debugging a machine with moving parts, that boundary is a wall you can see. The agent could read every electrical value inside my robot, thousands of times a second. It could not know the thing had smelled of burning plastic the week before.
That wall turned out to run exactly along the line between the parts of its work that were excellent and the parts that were confidently wrong.
The broken thing
I have a Reachy Mini. It’s a small desktop robot, mostly a head on a base.
The head doesn’t sit on a neck. It sits on six short legs arranged in a ring, each one a little motor that can extend or retract. Push some legs out and pull others in, and the head tilts, turns, rises, nods. Six legs working together give it full freedom of movement.
This is a standard bit of robotics geometry called a Stewart platform, and one property of it matters for this story: the legs are not independent. They’re rigidly linked through the platform, so each one is always working against the other five.
Which means a dead leg doesn’t cost you one-sixth of the movement. With all six pushing, the head rises level. With one not pushing, that corner has nothing under it — so the head doesn’t just fail to lift, it hangs tilted over toward the dead leg. The other five can’t take up the slack, because they’re geometrically committed to each other.
One day the head stopped lifting. It would run its startup routine and just stay down. One LED, on one of the six leg motors, blinked red. The software running the robot logged exactly one useful line before giving up:
Motor 'stewart_5' hardware errors: ['Electrical Shock Error']
“Electrical Shock Error” is alarming and misleading. It is not a warning that the robot will shock you. It’s the motor manufacturer’s name for the motor detected an electrical fault in itself and shut down to avoid damage. The motor is protecting itself. It tells you almost nothing about what the fault actually is — bad power, bad wiring, bad motor, all produce the same three words.
So: one motor out of six, self-disabled, reason unknown. I gave an AI agent SSH access to the small computer inside the robot and asked it to find out what was wrong.
Over the next few hours it did some of the best diagnostic work I have watched a machine do. It also got the physical answer wrong three separate times.
What it was genuinely good at
I want to credit this properly, because the credit is the whole reason the rest of the post is worth reading. This is not a story about a stupid model.
These motors aren’t dumb actuators. Each one is a small computer that will tell you, on request, its temperature, its supply voltage, its position, how hard it’s currently working, and a set of internal fault flags. The agent could interrogate all nine motors in the robot directly.
First it took a full reading of all nine and found the fault was isolated to exactly one:
id name error volts temp current position
14 stewart_4 none 6.4 26 0 4.8
15 stewart_5 ELECTRICAL SHOCK 6.4 26 0 -48.4
16 stewart_6 none 6.4 27 0 10.2
The important thing here is what’s the same. The broken motor is getting identical voltage to its neighbours, and it’s at identical temperature. It’s not starved of power and it’s not cooking. It also answered when spoken to, which means the wiring carrying data to it is intact.
From there the agent knocked down six suspects, each by measurement rather than assertion: the robot’s power supply wasn’t dipping, the voltage reaching the motors was correct and even, the communication wiring was healthy, the software configuration was right, the motor wasn’t being commanded past its allowed range, the firmware matched its siblings, and nothing was overheating.
Then it did something I didn’t expect. It designed a controlled experiment.
The idea is simple. Ask the motor to move, in tiny increments, while steadily increasing how hard you’re driving it — and watch how much electrical current it draws. Current is the honest number here. It’s the electrical equivalent of watching how hard someone is actually straining. You can’t fake it and the motor can’t misreport it.
Driving power went up seventeen-fold. The current didn’t move at all:
step 1 commanded +0.4° actually moved 0.00° current 51 drive power 10
step 8 commanded +3.2° actually moved 0.00° current 51 drive power 60
step16 commanded +6.4° actually moved 0.00° current 51 drive power 117
step24 commanded +9.6° actually moved 0.00° current 51 drive power 175
Think of it as flooring the accelerator and watching the fuel gauge not move. The motor is being told to try seventeen times harder and it is drawing exactly as much electricity as before, and it isn’t going anywhere.
But a number like that is meaningless in isolation. So the agent ran the identical test on one of the five healthy legs, on the same wiring, same power, same code:
step 1 commanded +0.4° moved +1.7° current 20 drive power 28
step12 commanded +4.8° moved +5.1° current 35 drive power 68
step24 commanded +9.6° moved +9.9° current 64 drive power 115
That’s what working looks like. It goes where it’s told, and the harder you drive it, the more electricity it pulls.
Put the two on the same axes and the difference stops being a wall of numbers:
The contrast is the finding. And it kills the most intuitive explanation: that the leg was mechanically stuck.
A jammed motor is the opposite of this. It pulls enormous current — it’s straining against something, and the harder you drive it the more it draws, right up until it burns out. This motor did the reverse. Later tests were starker still: at full drive it pulled zero current.
Not a strained motor. A motor that isn’t electrically there at all.
That is a genuinely well-built piece of work. Isolate the variable, build a control, produce a signature that means something. I would not have got there faster by hand.
And then it wrote this.
“Definitively exonerated”
I asked a simple question: should I try swapping the cable?
The agent reasoned it through. These are sealed units; the electrical activity that actually drives the motor happens inside the sealed body and never travels down the cable; the cable only carries power and instructions. Therefore no cable fault could produce this signature. Therefore the cable was “definitively exonerated.” It advised me not to bother checking.
The reasoning was clean. The confidence was unearned.
Its supporting argument was that zero current proves nothing is being lost in the cable. But that zero was measured after the motor died. A cable that had caused the failure would produce exactly the same zero afterwards. The argument ran backwards in time, and no amount of additional measurement would have caught it, because the error wasn’t in the measurement.
I want to be precise about the failure mode, because it’s the point of the post.
The agent did not have bad data. It had far better data than I did. What it did was attach the word definitively to a claim about the physical condition of an object it could not see, touch, or open — on the strength of measurements that were entirely correct, but that supported a much weaker claim than the one it made.
It did this three times. And each time, the correction came from me, and none of my corrections were technical.
Three facts that were never going to be in the data
The first was a smell.
In the days before the final failure, the robot had smelled of burning electrics. I mentioned this late, and only in passing, because it hadn’t occurred to me that it was a diagnostic fact. It was just a thing I’d noticed.
It reframed the entire case. The problem was no longer “a motor stopped working.” It was “something burned.” That gives you a physical event, a location, and a mechanism, and the agent’s whole model reorganised around it.
The second was how it felt in my hand.
The agent asked me to disconnect the power and move the dead leg by hand, and tell it whether the leg felt stiffer than the others. It didn’t. It felt completely normal.
That sounds like a nothing observation. It isn’t — and it’s the one place where the agent’s electrical knowledge and my hands genuinely combined.
There are two ways a motor like this dies:
- Shorted. Its internals fuse together, the motor becomes its own brake, and the shaft gets noticeably hard to turn.
- Open. Its internals burn through. Nothing is connected to anything, and the shaft spins freely — exactly like a healthy one at rest.
Normal to the touch meant open, not shorted. And an open circuit was compatible with a cable fault — so my hands revived the hypothesis the agent had “definitively” killed. It withdrew the backwards-in-time argument.
The third closed the case, and it wasn’t a measurement at all.
I eventually told the agent something I knew only because I’d had the robot open: the motors aren’t wired to a central hub in a star. They’re daisy-chained. Old Christmas lights. Motor 4 connects to motor 5, motor 5 connects to motor 6. Power and instructions for motor 6 physically pass through motor 5 — in one of its sockets, across a track inside its body, and out the other.
Motor 6 works perfectly. Full range, both directions, drawing healthy current the whole way.
Follow what that proves. Every electron reaching motor 6 has already travelled down the cable to motor 5, through both of motor 5’s connectors, and through motor 5’s internals. If any of that were faulty, motor 6 would be dead too.
And motor 6 draws more current than motor 5 fails at. So the shared path isn’t just intact — it’s proven good at a heavier load than the one that kills motor 5.
Which leaves exactly one thing that isn’t shared: motor 5’s own drive circuitry, downstream of the pass-through. That single conclusion accounts for every observation at once — communication fine, readings fine, no movement, no current under load. The cable was excluded at last, on sound grounds.
Now notice what that fact was. Not a sensor reading. Not a log line. Not anything in the manufacturer’s documentation, which doesn’t mention it. It’s the order the parts are physically wired in — and the only reason it existed anywhere was that a human had looked inside the box.
The silence was nobody looking
One more failure, and this is the one that generalises furthest beyond robots.
On a fresh restart, the agent checked the logs and found zero hardware errors. The supervising software hadn’t flagged motor 5 the way it had on every previous boot. Reasonable reading: something changed, maybe it recovered.
It then read the motor directly. Still faulted. Still dead.
The explanation was operational and I had to supply it. I hadn’t launched the app that puts the robot into its active state, so the supervising software never got as far as checking the motors. There was nothing in the log because nothing had looked.
That’s the shape of the mistake, and it has nothing to do with hardware. An agent reading your logs will treat absence as evidence unless something tells it the instrument was switched off. Empty error log, monitoring that never fired, a clean dashboard — each of these has two possible causes, and only one of them is “everything is fine.”
It’s also why the best evidence in this whole case was gone before I started. The robot’s system logs don’t survive a reboot; only the current session is kept. So the entire lead-up — weeks of it — existed nowhere but in my head: the head driving up and sticking there mid-routine, the robot spontaneously rebooting, several times, and then the smell.
If your system discards its history, the operator’s memory is the log. It is a bad log. It is the only one you have.
Don’t forward it as proof
There’s a near-miss in this story that bothers me more than any of the wrong conclusions, because the wrong conclusions only cost me time.
At one point the agent drafted a warranty email to the manufacturer. Inside it was a clean, specific, technical-sounding assertion: that the fault re-latches on its own with torque disabled. It then built an argument on top of that.
It wasn’t true. The agent’s own later test — 185 seconds of polling with torque never enabled — showed no re-latch at all. To its credit it caught this itself and retracted the claim. But it caught it after writing it into a document addressed to someone else.
If I had sent that email, I would have asserted a symptom that never happened, to a manufacturer, as part of a claim. And the person on the other end would have had no way to know. They’d have done the right thing — taken it seriously, tried to reproduce a behaviour that doesn’t exist, and burned their afternoon on it. The same goes for “definitively exonerated.” Sent to support, that phrase would have argued against the vendor’s own documented procedure — which asks you to check the cables — using a conclusion the agent had invented.
Notice where the cost lands. I get the speed. Someone else gets a confident, fluent, well-formatted document they cannot audit, because the prose gives no indication of which sentences were measured and which were guessed. It reads uniformly authoritative all the way through. That’s precisely the property that makes it dangerous once it leaves your hands.
Open source maintainers are already living this, buried in LLM-written bug reports and security disclosures that are articulate, plausible, and hollow. Support engineers are next. Every one of those costs a real person real hours, and the sender usually never finds out.
So: use it for discovery. It is genuinely excellent at generating hypotheses, designing experiments, and telling you what you haven’t checked yet. That’s the job.
But when something goes out — a support ticket, a warranty claim, a bug report, an incident writeup, a message to a colleague — send measurements, not conclusions. Numbers, commands, and raw output the recipient can re-run. Keep the narrative in your own voice, and assert only what you would be willing to defend yourself, out loud, without the agent in the room.
If you wouldn’t stake your own name on a sentence, don’t make someone else spend their day on it.
What I’d take from this
Confidence doesn’t shrink when the evidence runs out. The word “definitively” appeared in a written report about a component the agent could not see. The measurements underneath it were correct. That’s what makes it dangerous — there’s no visible seam. The reasoning stays articulate right across the boundary from measured to guessed, and nothing in the tone marks the crossing.
So ask where the boundary is. “Which of these claims did you measure, and which did you infer?” is a cheap question and it partitions the report exactly where the risk lives. Every wrong turn in this case was in the inferred layer. The measured layer held up start to finish — including the zero-torque test above, where it disproved its own written claim unprompted. It could falsify itself, but only inside the region it could reach, and only after the claim was already on paper.
Volunteer the things that don’t seem relevant. The smell and the pre-failure reboots arrived last and were worth more than every measurement combined. I wasn’t withholding them. They just didn’t feel like technical facts. Smell, sound, how something feels to move, “it used to do this,” “I skipped the usual startup today,” “I’ve had it open, and here’s how it’s put together.” That last category is the one people never think to mention and it’s the one that closed this case.
None of this is specific to robots. The software equivalents are everywhere:
- Someone restarted that box by hand last Tuesday.
- This config was edited in the console, not in the repo.
- The customer said it started right after the power cut.
- That queue has always been slow on Mondays.
Nobody logged any of it. And the agent will never ask, because it doesn’t know the question exists.
The split was real, and it wasn’t even. The machine was better than me at breadth and rigour — nine devices in one pass, a proper control, a seventeen-fold ramp with current sampled throughout. I was better at everything physical and everything historical. Neither of us gets to the answer alone. But of the two of us, the facts that actually decided it were all mine, and not one of them required any expertise. I smelled something. I touched something. I remembered something. I’d looked inside the box.
One caveat, stated plainly: the detailed technical report this post draws on was written by the same agent that made the mistakes, which means it is partly grading its own work. I’ve kept the measurements, which are checkable, and I was the one supplying every correction. Weigh the self-assessment accordingly.
The motor has a burned-out drive stage. It’s under warranty.
Appendix — the technical detail
For anyone who landed here searching Electrical Shock Error on a Dynamixel XL330. This is the part I wrote down so you wouldn’t have to repeat it.
Verdict
The output stage of stewart_5 (XL330-M288-T, ID 15) has burned open. It is not software, configuration, calibration, cable, connector, bus or power supply. Rebooting and reflashing will not fix it. The motor needs replacing.
The signature: no torque at all, and 0 mA drawn at non-zero PWM. That’s an open circuit — not a jam, and not a short.
Setup
| Robot | Reachy Mini Wireless, software v1.9.0 |
| Host | Raspberry Pi CM4, kernel 6.12.62 |
| Motors | Dynamixel XL330-M288-T |
| Bus | /dev/ttyAMA3, 1 Mbaud, via rustypot’s Xl330PyController |
| IDs | 10 body rotation · 11--16 stewart_1..6 · 17/18 antennas |
| Faulty unit | stewart_5 = ID 15 |
| Control unit | stewart_4 = ID 14 |
What the error actually is
- Register
70is Hardware Error Status. Bit 4 (0x10) is Electrical Shock. shutdown_error: 52= bits 2, 4, 5 (Overheating, Electrical Shock, Overload) are configured as latching shutdowns.- Once latched, the motor kills its own torque and blinks red until rebooted or power-cycled.
- A Dynamixel reboot clears
0x10→0x00, so the unit is not bricked. It re-trips within about six steps of any real load.
Ruled out by measurement
| Suspect | Finding |
|---|---|
| Pi power / brownout | vcgencmd get_throttled = 0x0; no undervoltage in dmesg |
| Motor supply rail | 6.4–6.5 V on every motor; ID 15 identical to healthy legs |
| Serial bus / comms | All nine ping; control loop stable ~49.6 Hz, nb_error: 0 |
| Software / config | All six legs already correctly configured; shutdown=52, mode=3 |
| Position limits | ID 15 at raw 1492 vs floor 1252 → ~21° headroom, not clamped |
| Firmware | v53 across all six legs, uniform |
| Overheat / overload | Only bit 4 set; 26 °C; no overload bit |
Don’t chase the 6.4 V rail. It looks high for an XL330 and it’s an easy false lead. From Pollen’s own docs: “We are using a higher voltage on Reachy Mini, it’s on purpose :)”
Test 1 — PWM ramp
Does current respond to drive?
Slow 0.4°/step ramp, PWM raised 17× (10 → 175, ~20% duty).
Result: zero motion across 9.6° of command with ~21° of headroom available. Current pinned flat at 51 mA, peak 52 mA.
Reading: rules out a mechanical jam. A jammed XL330 at PWM 175 would pull hundreds of mA climbing toward its 1.75 A limit, and current would scale with PWM. This did neither.
Test 2 — supply sag
Is the rail collapsing under load?
Ramp both legs while continuously sampling input voltage at the motor.
=== id15 dir=+1 === tripped step 2 pwm=18 cur= 0mA volt 6.4-6.6V
=== id15 dir=-1 === tripped step 1 pwm=-5 cur= 0mA volt 6.1-6.5V
=== id14 dir=+1 === 24 steps OK pwm=115 cur= 64mA volt 6.4-6.5V
=== id14 dir=-1 === 24 steps OK pwm=-118 cur=-66mA volt 6.3-6.5V
Reading: 0 mA at non-zero PWM is the signature of an open winding or a dead output stage. The healthy control pulled more current with less voltage variation.
Caveat: each voltage sample is a serial round-trip of roughly a millisecond, and ID 15 tripped within a few hundred milliseconds. This rules out a sustained sag, not a sub-millisecond one.
Test 3 — zero torque
Does it fail on its own, or only under load?
Latch cleared, then ID 15 polled every 5 s for 185 s with torque never enabled.
Result: no spontaneous re-latch.
Reading: the fault is load-dependent, not present at rest. This is also the test that falsified the agent’s own earlier written claim that the fault re-latches with torque disabled.
Why the cable is excluded
Dynamixel chains bridge VDD, GND and data across a motor’s two ports. All power and data for ID 16 therefore passes through ID 15’s connectors and its internal pass-through traces.
- ID 16 completes 24/24 ramp steps, both directions, drawing 64–75 mA.
- So the cable feeding ID 15, both connector contacts, and ID 15’s pass-through are sound and carrying current.
- They carry it at 75 mA — above the 0–52 mA range in which ID 15 fails.
- What remains is only what is not shared: ID 15’s own drive stage, downstream of the pass-through.
That accounts for every observation at once — comms fine, register reads fine, 0 mA at non-zero PWM, no torque, error latching under drive and absent at rest.
Likely mechanism
This part is inference about the inside of a sealed servo, not measurement.
Winding insulation degrades under cumulative thermal cycling into a turn-to-turn short. From there:
- A motor drawing abnormal current and unable to hold torque makes the head stick — one bad leg leaves the other five over-constraining a parallel platform.
- The current spike browns out the CM4, causing the spontaneous reboots.
- The short burns through (the smell), then goes open (the measured state).
- An open circuit draws nothing, which is why both the smell and the reboots stopped.
- Twenty-plus power cycles since, with no recurrence.
The measurements are the durable part.