← Back to blog
September 10, 2026Colm Byrne
Evidence boundary. Older posts may discuss prototype designs, patent claims, or planned integrations. The demonstrated v1 release is ccf-core/ccf-agent v1.0.1: hard min-gate coupling, QAC trust updates, and runtime certificates. Gate C exercised the computed runtime on Seed-class ARM hardware with driver-fed input. Unless a post cites a specific run, do not read it as proof of live sensors, mBot2 behaviour, Cognitum store validation, or production deployment.

The robot said low battery. The battery said sixty percent.

A real moment

Deirdre Mullen manages the night shift at a supermarket on the Pearse Road in Sligo. Eleven at night to six in the morning, five nights a week, and for the last eight months the floor scrubber has done the aisles while she does the stock. It is a good machine. It knows the floor better than the agency lads who come and go.

On Tuesday it stopped halfway down aisle seven, turned, and drove itself back to the charging bay. Twenty minutes later it came out, went at aisle seven again, and did the same thing. Then a third time.

Deirdre went and looked at the log in the morning. Three lines, all the same: low battery.

She checked the charge readout. Sixty percent.

Now she has a decision to make and nothing useful to make it with. Either the log is wrong or the battery is dying in a way the readout does not show. She is not a robotics engineer and it is not her job to be one. She books an engineer, loses a night of scrubbing to the visit, and the engineer finds nothing, because there was nothing wrong with the battery.

Here is what was actually happening. The chilled cabinets in aisle seven had been serviced on Tuesday afternoon. The compressor note changed. Not loud, not anything a person would remark on, just not the sound the robot had eight months of. Every pass down that aisle made the robot a bit less settled. By the third pass, with the charge low enough to be adding a little of its own weight, the two things together were enough to send it home.

Neither one on its own would have done it. The low charge on its own would not have. The strange compressor on its own would not have. Together they did, and the log had one box for the reason, so it picked one and wrote it down.

That is the whole problem in one line: a robot that leaves for two reasons at once, and a record that can only hold one.

Why this matters

There is a design idea in robotics that is genuinely lovely, and it is the reason this happens.

You could write a rule: if battery drops below thirty percent, abandon task, return to dock. Everybody does it that way. It works, it is easy to test, and when the robot goes home you know exactly why, because the rule says so.

Or you could do something better. Give the robot a running sense of unease. Let a low battery feed into it, gently at first and then steeply once the charge is really going. Let the robot prefer, when that unease is high, whichever place it knows best. Its charging bay is the place it knows best, because it spends more steady, quiet, consistent time there than anywhere else in the building. So the robot goes home when it is running low, and nobody wrote a rule to make it.

The reason this is better is that the same unease goes up when something odd or unpleasant happens near the robot. Which means the robot also goes home when it is unsettled, and again nobody wrote a rule. One system, two behaviours, and the second one came free.

That is the design in the CCF filings, and I still think it is the right one. But it has a consequence that I had not measured until this week, and it is not a small one. Both behaviours look the same from outside. The robot puts down the work and goes to its bay. If you want the logbook to say which it was, the logbook has to be able to tell them apart.

Everything downstream depends on that logbook line. Whether an engineer gets booked. Whether a maintenance budget goes on batteries or on refrigeration. Whether a care home decides a robot is unreliable around a particular resident. The research on human-robot teams is clear that the kind of failure matters more than the rate of it. People forgive a machine that explains itself and stop trusting one that does not.

What CCF does about it

So I built it and ran it. The robot, the charging bay, the unease, the passing noises. Two hundred simulated shifts, four thousand steps each, both behaviours live at once. Fifteen thousand seven hundred and thirty one times the robot put down its work and went home.

Then, and this is the part that mattered, I worked out the real reason for each one without asking the robot. At the moment it turned for home, ask two separate questions. Would the battery number alone have been enough to turn it? Would the disturbance alone have been enough?

Four possible answers. It was the battery. It was the disturbance. It was either one, both were enough on their own. Or it was neither on its own, and only the two together did it.

That last case turned out to be most of them. Seventy five percent. Three withdrawals in four had no single true reason. Not an ambiguous reason, not a hard-to-determine reason. There was no single reason, because neither thing alone would have moved the robot.

Once you know that, the logbook question answers itself. A record with one box for the reason is wrong three times in four, and no amount of care in filling it in helps, because the true answer does not fit in the box.

I tested four kinds of record against that truth. One where the robot writes its own reason. One that stores the total unease and the charge level and works backwards. One that stores both numbers separately but still has to write one reason. And one that stores both numbers and is allowed to say "both".

The first three all scored the same: right about a quarter of the time. The fourth was right every time.

That result surprised me. I had assumed that storing more detail was what would help. It was not. The record that stored both numbers and could only write one reason did exactly as badly as the record that stored nothing but a label. Storing more numbers bought nothing at all. What bought everything was letting the answer have two names in it.

The numbers

Fifteen thousand seven hundred and thirty one withdrawals. Twenty three percent were genuinely the battery. One percent were genuinely the disturbance. Seventy five percent were both together, and no single-reason record can be right about any of them.

Accuracy came out at 0.2452 for all three single-reason record formats, and 1.0000 for the one allowed to name two causes.

Then I tried tampering with the records. Rewriting the reason on every third event, and nothing else. The record that only holds a written reason caught none of them, all five thousand two hundred and forty four went through, and it is not that the check was weak. There is no check. There is nothing in that record for the label to disagree with. Every format that held a second number caught all five thousand two hundred and forty four.

A harder attack: forge the battery number itself so the event reads as a genuine low-battery return. That one gets caught by comparing the claimed battery contribution against the charge the robot reported, and how well it gets caught depends entirely on how finely the battery gauge reports. A gauge reporting in ten percent steps let four in ten forgeries through. A gauge reporting in one percent steps let none through.

There is a nice detail in that. A false low-battery claim is cheapest to make when the charge is genuinely around a quarter, which is also when low battery is a plausible thing to claim. It is most expensive at high charge, where it is least plausible. Which is why Deirdre's sixty percent readout is the thing that gives the game away.

The extra cost of the record that gets everything right: two nanoseconds per event instead of one, and thirty three bytes instead of nine. For one robot leaving its work once an hour, that is two hundred and eighty two kilobytes a year against seventy seven. There is no version of this where the storage is the reason not to do it.

What this doesn't solve

If the robot reports a false charge level in the first place, everything above falls over. The battery contribution computed from a false charge agrees with that false charge perfectly, and nothing in the record catches it. This work does not address that and should not be read as addressing it.

The definition of cause I used, would this thing alone have been enough, is one definition. It is a reasonable one and it is the one the numbers are measured against, but somebody could reasonably use a different one and get different numbers.

Seventy five percent is what these settings produced. I swept the settings and the both-together case dominated everywhere I looked, never dropping below about seventy one percent, but I am not claiming a universal figure.

And two causes is the easy version. Real robots will have more things feeding their unease than two, and the test I used gets more expensive as that number grows. I have not measured how much.

For the curious, the math

The battery contribution is a sigmoid: `tau_energy = tau_max / (1 + exp(k * (SoC

  • SoC_mid))), with the filed defaults tau_max = 0.8, k = 12, SoC_mid = 0.25`. Near zero above half charge, steep below a fifth. It is added to the running tension value as one summand alongside whatever the environment is contributing, and the robot leaves when the total makes its highest-coherence context the preferred one.

One thing that fell out of the run and that I did not expect. Because the battery contribution rises smoothly as charge falls, and because the environmental contribution is never quite zero, the sum always reaches the leaving threshold before the battery contribution reaches it on its own. Which means a pure low-battery return is impossible unless the reported charge jumps past the threshold in one step. And the only thing that makes it jump is the coarseness of the battery gauge.

At two percent gauge steps and finer, zero of about sixteen and a half thousand withdrawals were purely the battery. None at all. A robot with a good battery gauge, working somewhere with any background disturbance at all, never has a pure low-battery return, and a fleet log that reports one is describing something that did not happen.

The last thing I checked was whether the CCF runtime certificate settles any of this. It does not, and I think that is worth saying plainly rather than leaving it implied. The certificate returned an identical residual and an identical status for a battery-caused withdrawal and a disturbance-caused one. It answers whether the update the robot made to its own state was the correct one. It is never asked why the unease rose and it does not answer that.

Where this fits

This is one night's work in a series that walks through the CCF filings one piece at a time and measures what each one actually does. This one is Extension 5 of the second provisional, US 63/994,113, the part that makes going home for a charge a behaviour that falls out of the dynamics rather than a rule, together with Extension 7 of the fourth, US 64/039,655, which is the record the robot keeps of why it did what it did.

The full write-up, with every table and the reasoning behind each number, is in the research doc on the CCF repository, and the code is a small Rust crate you can run yourself. The architecture decision that came out of it is ADR-0012: store the measured pieces, never store a conclusion, and work out the reason on the reading side where somebody can disagree with you.

More at floutlabs.com and theshyrobot.com. The certificate crate is public at crates.io/crates/ccf-core.

License and contact

BSL 1.1, converting to Apache 2.0 in 2032. Contact via floutlabs.com.