All articles

The Last Mile Is Where the Hours Went

The AI finishes the report in ninety seconds. Then you lose two hours moving it into a format a client can open. The real cost was never the thinking. It was the last mile. Here’s how I closed it.

The AI finished the thinking. Then the real work started.

The report was done in ninety seconds.

Then I spent two hours on it. Pasting the model’s output into Word at eleven at night, rebuilding tables it had mangled, chasing a heading that wouldn’t hold its formatting, so a client could open the thing the next morning and not know a machine had been anywhere near it.

The clever part took a minute and a half. The stupid part took the rest of the evening.

This is the joke nobody tells you about working with AI. You watch a demo where a model writes a full diagnostic in the time it takes to make a coffee, and you think the bottleneck just moved. It didn’t. It slid downstream and sat there, out of shot, where the demo never films.

Because the model gives you text. Beautiful, structured, correct text. In a format that no client, no board, no GM has ever opened in their life.

I call it the last mile because that’s what it is. In logistics, the last mile is the bit that kills you. Getting a parcel across the country is cheap and solved. Getting it up the right set of stairs to the right front door is where most of the cost hides. AI is the same shape. Getting from a blank page to a finished argument is the part everyone celebrates. Getting that argument into a thing a human being can read, mark up and sign off is the part that eats your night.

For months I treated that as the price of the tool. The model does the heavy lifting, and I do the carrying. Fair trade. I told myself the two hours in Word were “polish”, which is the word you use when you don’t want to admit you’re doing unpaid manual labour on top of a job that was already finished.

Then I did the sum. On a normal report I was spending more time formatting the output than the model spent producing it. Sometimes ten times more. The smartest thing in my workflow was waiting on the dumbest.

That’s the problem worth solving. The thinking is fast and getting faster. The handoff is slow and nobody’s touching it. So the handoff is where all the time now lives, and it’s the last place anyone looks.

Markdown is where AI output goes to die.

Ask a model for a report and it gives you markdown. Markdown is a plain-text way of writing formatted documents. Hashes for headings, pipes and dashes for tables, asterisks for bold. Developers love it because it’s clean, it’s portable, and it plays nicely with the tools they already live in.

Your client has never heard of it. Your client lives in email, in a browser, and in Word. Hand them a markdown file and you’ve handed them a page of text with hash symbols in front of the headings and rows of dashes where a table is trying to be. It looks like something broke.

So you don’t hand it over. You translate it first. You copy it out, you paste it into Word, and you watch what survives the trip.

Not much does. The headings come across as plain bold text and lose their structure. The tables arrive as a wall of pipe characters that Word has no idea what to do with, so you rebuild each one by hand. The careful nesting of the argument flattens into an undifferentiated slab. Anything that was meant to be interactive, any link, any collapsible bit of detail, is gone before you started, because it was never going to survive as static text on a page.

You end up rebuilding the document the model already built. You are the printer. The AI wrote the file and you are the one physically pushing it through the last mile, one mangled table at a time.

And the worst version isn’t even the reformatting. It’s the review.

Say the report survives its trip into Word. Now you send it to a client for comment. What comes back? A reply-all thread where three people quote different paragraphs and contradict each other. A version of the file with tracked changes that don’t merge cleanly. A voice note. A “can you jump on a quick call”. Feedback scattered across four channels, none of which points cleanly at the thing it’s talking about, all of which you now have to gather up and reconcile by hand before you can do a single edit.

The model gave me a finished argument in ninety seconds. The format then cost me the ninety seconds many times over, at both ends. Getting it out, and getting the feedback back in.

Markdown wasn’t the villain. It’s a fine format for the place it belongs, which is between machines and developers. The mistake was letting it be the thing I shipped. I was taking a working format and treating it as a delivery format, and then paying the difference with my evenings.

Stop tuning the prompt. Change the container.

The reflex, when AI output isn’t landing, is to go back and fiddle with the prompt. Reword the instruction. Add an example. Beg the model to format the tables properly this time. I spent a while there. It’s a trap. You can make the markdown marginally cleaner, but you can’t make markdown into something a client can open, because it was never built to be that.

The thing I’d got wrong was the layer I was working at. I was tuning the message. The problem was the envelope.

So I changed the envelope. Instead of asking the model for text and then carrying that text somewhere it could live, I gave it somewhere to live in the first place. One document container, built once, that the model writes straight into. A single HTML file.

Not a website. Not a system. One file, the kind you can double-click and it opens in whatever browser you’ve got. No server humming somewhere. No login. No app to install. Nothing to keep running. You can email it, drop it in a shared drive, or open it on a plane with no signal, and it behaves the same every time, because everything it needs is inside the one file.

That last part matters more than it sounds. Most of the “interactive document” tools out there are really a login and a subscription wearing a document costume. The content lives on someone’s server, and the moment your client is offline, or the link expires, or the vendor changes their pricing, your deliverable stops working. A single self-contained file has none of those dependencies. It’s yours. It works in ten years. It works on a laptop in a basement.

And it’s branded. It opens with my colours, my type, my name in the corner, the same way every time, without me touching a template. It looks like a considered document because it is one, from the first pixel, before I’ve written a word into it.

The shift sounds small. It isn’t. I stopped treating the AI’s output as raw material I had to process, and started treating it as a finished object the moment it left the model. The container does the presenting. The model does the thinking. I stopped standing in the middle doing the manual labour of turning one into the other.

The two hours in Word went to zero. Not “got shorter”. Went away. There’s no export step anymore, because there’s nothing to export from. The thing the model produces is the thing the client opens. Same file. First try.

The deliverable now reviews itself.

Because the container is a live document, it can carry its own tooling. The review process I used to run over email, across four channels, chasing quotes and reconciling contradictions by hand, now lives inside the document. The deliverable reviews itself.

Open one of these files and every section has a little status on it. Unreviewed to start. The reader clicks through it once and marks it reviewed. If something’s wrong or they’ve got a question, they flag it instead. There’s a running tally at the top: eight of twelve reviewed, one flagged. So anyone opening the file can see at a glance how far the review has got and where the trouble is, without asking a soul.

If they want to say something specific, they add a note straight onto the section it belongs to. Not a comment in a separate thread that references “the bit about staffing”. A note pinned to the bit about staffing itself, sitting right there next to it, gathered with every other note in a panel down the side so nothing gets lost.

I watched this land with a client recently. A GM, the kind who runs three meetings before lunch and reads documents in the ten-minute gaps between them. Old world, she’d have skimmed the report on her phone, fired back a two-line email saying “looks good, couple of thoughts, let’s talk”, and I’d have spent the call trying to work out which thoughts attached to which page. This time she opened the file, worked through it in the gaps, marked nine sections reviewed, flagged two, and left a note on each flag saying exactly what she wanted changed. Then she exported the lot and sent it back before her next meeting started. The review took her the dead time between two calls, and it arrived on my end as a clean set of instructions with nowhere for ambiguity to hide.

Then, when they’re done, they export the whole lot as one small file and send it back. Every status, every flag, every note, in one object. I open it and their entire review drops into my copy exactly as they left it. No transcribing. No hunting through an email chain trying to work out which comment lands where. The feedback arrives already attached to the thing it’s about.

That export is the part that surprised me most. There’s no account behind any of this, and no shared server keeping everyone’s copy in step. The whole review lives in the file on the reader’s own machine, and the export is the only handoff. It’s a small block of text they paste into an email or a message. It survives the file being copied, renamed, forwarded, opened on a different laptop next week. Nothing to log into, nothing to expire, nothing a vendor can switch off from their end. The review travels the same way the document does, because it’s built from the same single-file idea.

And when it’s time to hand a clean version to someone who doesn’t need to see any of that, the review machinery, the whole reviewing side, prints away to nothing. One button, and you get a clean, branded PDF. No status dots, no notes, no flags, none of the working. Just the finished document, laid out properly, ready for the board pack. The same file serves the messy middle of the work and the polished end of it, and you never rebuild anything to move between the two.

Sit with what that removes. It removes the reply-all thread. It removes the tracked-changes file that won’t merge. It removes the call that exists only because a comment had nowhere better to live. It removes the evening I used to spend gathering scattered feedback into one place so I could start editing. All of that was the last mile at the other end, the return leg, and it was costing me as much as the reformatting ever did.

None of this is exotic. It’s the sort of thing a good software product has done for years. The difference is that it now ships inside a single file that a language model writes into, and it costs me nothing per document, because I built it once.

Here’s how it’s built.

So how does a model produce something like that? It doesn’t, and that’s the trick.

The model is good at writing content. It’s a probability engine, and content is exactly the sort of fuzzy, judgement-shaped work it’s built for. What it’s bad at, and what you never want it improvising, is the machinery: the navigation, the review tooling, the code that makes the status dots work and the notes save and the PDF print clean. That stuff has to be right the same way every time. It’s the deterministic backbone. You don’t want a probability engine rewriting your plumbing on every job, getting it slightly different, slightly broken, each time.

So I split the two apart.

There’s one shell. Built once, by hand, and then left alone. The shell holds all the machinery: the layout, the branding, the navigation down the side, the review tooling, every line of code that makes the thing interactive. It’s fixed infrastructure. It doesn’t change from document to document. It’s the delivery van, and it’s the same van whether it’s carrying a diagnostic, a business case, or a set of requirements.

The model only ever writes the cargo. It writes the content sections, the headings and the prose and the tables and the findings, and it drops them into the shell’s body. That’s the whole of its job. It never touches the machinery. It couldn’t get the interactivity wrong if it tried, because it isn’t writing the interactivity. The interactivity was already there, waiting, built into the container before the model showed up.

This is the part I’d tell anyone thinking about doing the same thing. The instinct is to ask the AI to build the whole document, tooling and all, from scratch, every time. Don’t. You’ll get something that works four times out of five and breaks on the fifth in a way you can’t predict, because you asked a probability engine to be a printing press. Build the reliable part once, by hand, and lock it. Let the model do only the part it’s genuinely good at, which is the words. Give the fuzzy work to the fuzzy tool and keep the exact work exact.

I know because I tried it the other way first. Early on I had the model build everything, container and content together, on every job. Most came out fine. Then one landed on a client’s screen with the navigation half-broken and a table running off the edge of the page, and I found out about it from the client. That’s the failure you can’t afford. Output that’s wrong in a way you didn’t catch, in front of the person you were trying to impress. Locking the shell means that particular fear is gone. The machinery can’t break on document forty, because it’s the same machinery that worked on document one, and document one was checked by a human who isn’t guessing.

That division is the whole design. Deterministic shell, probabilistic content. The bit that has to be identical every time is identical every time, because a human built it and it never moves. The bit that should be fresh every time is fresh every time, because the model writes it new. Neither one is doing the other’s job, and that’s why the output is finished when it arrives instead of nearly-finished and waiting on me.

There’s more to the shell than holding still. When the file opens, it reads its own contents and builds its own furniture. It walks the sections, assembles the navigation down the side, wires up every status control and every note field, and starts tracking which section you’re reading so the nav can follow you down the page. None of that gets written into each document. It’s the same code every time, run fresh in the reader’s browser the moment they open the file. The document puts itself together on the way in. And it remembers. Mark three sections reviewed, close the file, open it tomorrow, and the marks are still sitting there, held in the browser, waiting where you left them.

The payoff compounds. Every improvement I make to the shell, a better print layout, a cleaner way of showing notes, a new kind of diagram, lands in every document I produce from that day on, without me revisiting a single old one. The container gets better and the whole catalogue inherits it. Fix the van once, and everything you’ll ever ship in it rides better.

The whole review desk fits inside a browser tab.

Open one and the layout is the same every time: a header up top, a navigator down the left, the document in the middle, a review rail down the right, and a row of controls in the corner. Each part is doing a job, so it’s worth knowing what they are. Here’s the whole thing at a glance, drawn rather than described.

The Last Mile Is Where the Hours Went ExportImportPrintReset NAVIGATOR 6 of 9 reviewed The real workMarkdown diesThe containerReviews itselfHow it's built DOCUMENT The deliverable reviews itself Reviewed + note Two ways in Flagged REVIEW RAIL MARKDOWN DIES Remove opener line TWO WAYS IN Remove opener line
The whole file at a glance: header and toolbar up top, the navigator with its status dots and progress bar on the left, the document with per-section controls in the middle, and the review rail on the right.

Across the top is the header. The title, a line of subtitle under it, my branding, the date, and a short document id. It’s the bit that tells you at a glance someone built this on purpose. Nothing interactive, just the frame.

Down the left is the navigator. Every section listed in order, and clicking any one slides you straight to it, so nobody’s scrolling through eight screens to find the bit they care about. Beside each entry is a small coloured dot: grey while it’s unread, green once it’s reviewed, orange if it’s flagged. Above the list sits a progress bar and a running count, something like “six of nine reviewed, one flagged”, so anyone can see how far the review has got without asking. And as you scroll, the navigator keeps pace, lighting up whichever section you’re sitting in. You always know where you are.

Unreviewed grey, not looked at yet Reviewed green, read and cleared Flagged orange, something to fix
Every section holds one of three states. A click moves it to the next.

The middle column is the document itself. Each section carries its own controls on the heading. A status chip you click to move it through unread, reviewed and flagged. A small toggle that folds the whole section away once you’ve dealt with it, so you can hide what you’ve read and keep only the open work in front of you. And a ”+ note” button that opens a box to type a comment, pinned right there on the section it belongs to.

The deliverable now reviews itself Reviewed + note NOTE ON THIS SECTION Opener line is throat-clearing. Cut it, start on the example. Save
One heading, up close. The status chip, the collapse toggle, and a note pinned to the section it belongs to.

Down the right is the review rail. Every note anyone leaves gathers here in one column, each tagged with the section it came from. Click a note and it jumps you back to the spot it’s about. It’s the whole of the feedback in one view, the thing that used to live scattered across an email thread and a phone call.

Along the top sits a small row of buttons that run the machine. Export packages your entire review into one block of text to send back. Import takes someone else’s exported review and drops it onto your copy. Print strips all the review furniture out and hands you a clean branded PDF for the final version. And reset clears your marks so you can start the review again from scratch.

Reviewer's copy { "status": ... "notes": [ ]} one block of text Export Import My copy
The review travels as one block of text. Export from the reviewer's copy, import onto mine, and every mark lands where they left it.

Underneath all of it is the thing that makes it hold together. Every mark, every note, every folded section is remembered by the browser and held inside the one file. Close it and reopen it and your review is still there. Copy the file to another machine and it behaves the same. There’s no database, no server, no account. The document, the tooling and the review state all live in a single thing you can email. That’s the whole trick, and it’s why none of this needs more than a browser to run.

Two ways in: let the model write the HTML, or keep markdown as the source.

I’ve spent this whole piece telling you markdown is where AI output goes to die. And yet I still use markdown constantly, on purpose, and I’d tell you to as well. The trick is knowing what markdown is for.

Markdown is a fine source. It’s a terrible final product. Those are different jobs, and the mistake was only ever letting the source be the product.

So there are two ways content gets into the shell, and which one I use depends on what happens to that content afterwards.

The first way is the model writes the document straight into the shell as its final form. This is for the one-off deliverables, the things that get produced, reviewed and handed over, and then live their whole life as that one file. A diagnostic. A business case. A briefing. Nothing downstream needs the raw text ever again, so there’s no reason to keep a separate source. The model authors it into the container directly, and the container is the deliverable. Done.

The second way keeps markdown as the source of truth and pours it into the shell only at the end. This is for anything that has a life beyond the document. Some of my work feeds other things. A requirements document gets read, machine-read, by a tracker that pulls each requirement out and follows it. An article gets built into a website. A set of notes gets filed into a knowledge base that expects a particular structure. All of those downstream tools want clean, structured markdown, not a page of HTML. So for that work, markdown stays the master copy. It’s what I edit, what gets version-controlled, what the other tools consume. And when it’s time to give it to a human, a small converter reads the markdown and pours it into the exact same shell, with all the same tooling, automatically.

The document a client opens is identical either way. Same look, same review tooling, same clean print. They can’t tell which path it came down and they don’t need to. The difference is entirely behind the scenes, and it comes down to one question: does anything other than a human ever need to read this?

If yes, markdown stays the source and the HTML is a view of it, generated on demand and thrown away as often as you like, because the real thing is the markdown. If no, skip the middleman and let the model write the finished object once.

Holding both is what makes the whole thing durable. I’m not locked into HTML, and I’m not stuck shipping raw markdown. The presentation layer and the source layer are separate, and I move between them freely depending on the job. The container is a view. The source is the source. Keeping those two ideas apart is most of the reason none of this has painted me into a corner.

The real gain from AI isn’t speed of thinking. It’s the last mile.

We benchmark the wrong thing.

Every model launch is a race about how fast it thinks. Tokens per second, reasoning steps, how quickly it can churn out a first draft. And that stuff is genuinely getting faster, and it’s genuinely impressive, and it was never where my hours went.

My hours went to the last mile. The reformatting. The rebuilding of tables the model had already built. The gathering of scattered feedback into one place so I could act on it. The translating of a working format into a delivery format, at both ends of every job, by hand, at night. All the unglamorous carrying that happens after the thinking is done and before the client sees anything. None of it shows up in a benchmark. All of it showed up in my week.

So the thing that changed how I work with AI wasn’t a smarter model. I had a plenty-smart model already. It was fixing the mile the model was never going to fix for me. Give the output a container built for humans instead of machines. Let the model write only the part it’s good at. Build the reliable machinery once and lock it. Keep the source and the presentation as separate things. None of that is about intelligence. It’s about plumbing, and plumbing is where the time was hiding.

Here’s the test, if you want one you can run this week. Time the next piece of AI work you do end to end. Not the bit where the model writes. The whole thing, from the moment you ask to the moment a real person can read, review and sign off the result. Then look at how that time splits. My bet is the thinking is a sliver and the last mile is most of the clock.

That gap is the real prize. Everyone’s fighting over the ninety seconds. The two hours are sitting right there, unclaimed, and they’re the ones that were costing you your evenings.