Lede: I gave a language model access to my own mileage-log app. The hard part wasn't teaching it to understand "Tuesday, round trip to Brno." The hard part was deciding what it must never be allowed to do.

A mileage log is a document nobody wants to write, and plenty of people write it anyway. In the Czech Republic no law actually requires one — it's evidentiary practice. When the tax office asks why you deducted the car, the log is the thing you put on the table. (Slovakia went further this January and made electronic logging a condition of the full VAT deduction. The two rules have nothing in common except that they're both about cars, which doesn't stop search engines from mixing them up.)

Evidentiary practice has an unpleasant property: you do it long before you need it, and the reward for doing it is that nothing happens. That's exactly the kind of work people postpone until a deadline catches fire — and then do retroactively, from memory and a calendar, at the worst possible moment.

I run my own mileage-log app. For years I focused on the form. Make it fast, make it remember recurring routes, make it clickable one-handed on a phone. And it was still a form: empty fields waiting for someone to retype what they had already written down somewhere else. Because the source material exists. It's in the calendar, in trip notes, in the card statement from the petrol station. The data isn't missing. The translation is.

That's the part I shipped this July: the app now has an AI assistant you hand your raw material to in an ordinary conversation, and it prepares the trips. But the interesting part of the project isn't that a model understands a sentence. Everyone can do that now. The interesting part is where I had to tie its hands — and why that turned out to be the main thing I built.

A tax document does not tolerate a hallucination

A language model with database access and write permission is a wonderful demo and a poor idea. In a notebook a bad entry costs you nothing; you delete it. In a mileage log an entry is a claim that may end up, a year later, inside an answer to an official question. I don't want to be the person explaining to an auditor that this trip is in there because it looked statistically likely.

So I built it the other way round. The assistant does not write to the log. Ever. Everything it produces is a draft — a proposal that shows up in the app in its own "to review" queue and enters the log only when you click it. The model doesn't have write permission; it has proposal permission. The difference between those two is the entire product.

For a while I treated this as a limitation I'd have to apologise for. I was wrong — it's the best sentence I can say about the feature: nothing gets into your log that you didn't approve. It isn't a safety catch bolted on from outside, it's the shape of the thing. The machine does the tedious mechanical half; the human keeps the half that carries responsibility and takes three seconds. Nobody is surprised when an assistant prepares the paperwork and the signature stays with you. This is the same arrangement, except this assistant never sleeps and can't say no.

Where the model ends and the code begins

The second boundary was less obvious, and I think it matters more.

The assistant doesn't calculate the reimbursement. Not at all. When you ask for June's figures, the numbers don't come from the model — the application computes them from the statutory rates, with the same code that runs when you ask for the same thing by clicking. The model can't see into that calculation and has no way to influence it. It receives a finished result and, at most, narrates it.

There are two reasons, and both are worth saying out loud. The first: arithmetic from a language model is plausible, not correct — and a plausible number is worse than a missing one, because it doesn't prompt a question. The second is more interesting: the rates change every year. A model trained last year will get this year's rate wrong, and will get it wrong confidently, because its confidence has nothing to do with currency. The defence against that isn't a better model. It's not asking it the question.

What I took from it is a rule I now use elsewhere: let a language model translate, not decide. It's excellent at turning messy human input into structure — "Tuesday and Thursday, Brno and back, plus the airport run some time on Friday" is easy for it and impossible for a regular expression. But the moment that structure turns into a number somebody signs, the turn belongs to code you can read, test, and fix in January.

Seven verbs

The whole assistant rests on seven tools. It can add a trip, bulk-import source material, list trips, produce a monthly summary, calculate reimbursement, export the data, and list vehicles. That's it. Seven verbs.

I stared at that list trying to work out what was missing, and then realised the interesting information is what's missing on purpose: nothing in it deletes, and nothing overwrites an existing entry. That's not an oversight. If I gave the assistant a verb like "fix that trip from last week," it would look terrific in a demo and it would be a quiet way around the entire approval step — because an edit is a write with a friendlier name.

The tool surface isn't a technical detail. It's a statement about what the thing is even allowed to want. It may be the only security document that genuinely holds in an integration like this, because unlike instructions in a prompt, it can't be talked out of anything.

What broke

I expected the problem to be comprehension. It wasn't.

The problem was duplicates. A person dictating their trips speaks in references: "as usual," "same as last week," "and back." Send input like that twice — because you weren't sure it went through, which is very human — and the model will cheerfully prepare the same trips a second time. It has no way of knowing it has been here before. The fix isn't in the model: bulk import runs in two steps, first a preview of what would be created and only then a commit, and every request carries its own identifier so a retry can be told apart from a new intention.

The second thing was vagueness. "Last Tuesday" is unambiguous to a person and, to a machine that doesn't know what day it is, isn't. The model will always fill that gap — and that's the trap. It doesn't lack nerve, it lacks doubt. It never says "I don't understand this," it says a date. Which is why the draft queue earns its keep in a place I assumed would be a formality: a mistake made this way is always a visible mistake, because it sits in a list waiting for a click instead of lying inside the log as a fact.

The bug that wasn't in the model

The nicest failure in the whole project didn't happen in the AI. It happened in the plumbing.

The address the assistant listens on and the address of the page where a user approves drafts start with the same characters — one is a prefix of the other. The proxy rule routing the assistant's traffic was written as a prefix match. So it swallowed the other address too. The one page in the entire application whose purpose is human oversight disappeared behind the component it exists to oversee.

The fix was a one-liner: an anchored match instead of a prefix. But the episode stayed with me as the best summary of that month. I spent weeks thinking about how to stop a model inventing a trip, and was nearly taken down by rule ordering in a web-server config. The risk in these integrations isn't only the thing we now call AI safety. It's the ordinary, boring, hundred-year-old software bill for inattention — except it now hangs over a document the state takes an interest in.

What I don't know yet

I don't know whether people will use it. I genuinely don't, and I'm not going to paper over that with "early feedback is great."

I picked three signals that could prove me wrong, and all three are measurable. First, the ratio of approved drafts to created ones: if people throw the proposals away, the model understands the source material worse than I think, and the savings dissolve into corrections. Second, the gap between connecting and using — on servers I run elsewhere I've had my nose rubbed in the fact that incoming requests are not the same thing as interest, and I have no reason to think this is different. A connector can be added out of five minutes' curiosity and never opened again. Third, duplicates: if logs start showing two trips down the same route on the same day, my two-step guard leaks, and it doesn't matter how good it looks on paper.

I'm giving it until the end of the year. That's roughly how long it takes, in my small corner, to tell a feature people use from a feature that is merely well written.

Closing

I didn't stop filling in my mileage log because someone abolished it. I stopped because I split the work in two and put each half on the side where it's cheap. Retyping a calendar into fields is trivial for a machine and dull for a person. Deciding that this claim goes into a tax document is unreachable for a machine and one click for a person.

Nearly every good language-model integration I've seen so far looked like that. And nearly every bad one had the same thing in common: the boundary went missing somewhere — usually because the demo looks more impressive without it.

The application in question is Kniha Jízd, a Czech mileage logbook. The AI assistant has been part of it since July 2026 and is included in the paid plan; the logbook itself works without it.